redux+express+MongoDB 学习

一、react 开发环境

  • create-react-app 官方脚手架,生成文件格式,安装:
cnpm i -g create-react-app
  • 创建一个应用:
create-react-app yjw
  • 进入到 yjw 目录下:
    • 可以看到自动生成的几个文件:

README.md、package.json、src、node_modules、public

  • 执行命令:npm start
  • 在浏览器输入:localhost:3000
脚手架相关命令:

如何使用 create-react-app :
①npm install --save redux : 安装第三方库 redux;
②npm run eject : 生成配置文件,可以自定义配置 webpack;
③扩展 package.json 里的 script 字段,扩展 npm run 命令。

二、ES6 简单介绍

  • ES6 的详细学习请参照 ES6 的快速入门与使用
  • ES6 是新的 JavaScript 语法标准;
  • 使用 babel 语法转换器,使其支持低版本浏览器;
  • 流行的库基本都是基于 ES6 构建,React 默认使用 ES6 新语法开发。

1、块级作用域、字符串、函数

1.1、块级作用域
  • 定义变量使用 let 替代 var;
  • const 定义不可修改的变量;
  • 作用域:{}。
1.2、字符串
  • 模板字符串: 两个反引号,在两个反引号之间获取变量:${变量名}
1.3、函数
  • 可以设置参数默认值;
  • 箭头函数:
    • 参数只有一个时,参数外面的小括号可以省略;
    • 只有一条 return 语句时,可以省略 return 和大括号;
    • 可以保持 this 作用域。
  • 展开运算符。

2、对象扩展、解构

2.1、对象的扩展:
  • Object.keys、values、entries:
    • keys:获取到对象 key 组成的数组;
    • values:获取到对象 value 组成的数组;
    • entries:获取到两个数组,分别有对象的 key 和 value 组成;
    • 说明:定义一个对象时,键可加引号可不加引号,默认会被认为是一个字符串,如果想要用外面定义的变量作为 key,可按如下方式使用(计算属性):
const name = 'yijiang';
const person = {
  [name] : 18
};
  • 对象方法简写,计算属性:
const obj = {
  hello:function(){//之前写法},
  hello1(){//现在写法}
}
  • 展开运算符(不是 ES6 标准,但是 babel 也支持)
//合并两个对象
const obj1 = {name:'yijiang',age:18};
const obj2 = {score:100,height:180};
const obj3 =  {...obj1,...obj2};
2.2、解构赋值
  • 函数也可以多返回值了:
    • 数组解构;
    • 对象解构。
  • 数组解构:
const arr = ['yijiang',18];
const [name,age] = arr;
  • 对象解构:
const obj = {name:'yijiang',course:'computer'};
const {name,course} = obj;

3、类、模块化等

3.1 类
  • 提供 class 的语法糖:
    • 是 prototype 的语法糖;
    • extends 继承;
    • constructor 构造函数。
  • ES6 中新出现的数据结构:
    • Set,元素不可重复;
    • Map;
    • Symbol。
3.2 模块化
  • ES6 中自带了模块化机制,告别 seajs 和 require.js
    • export , export default;
    • import {},import;
export 的内容,在 import 时,需要使用 {} 来导入变量名/类名;
export default 的内容,在 import 时直接导入变量名/类名即可。
  • node 现在还不支持,还需要用 require 来加载文件。
3.3 其它:还有一些特性,虽然不在 ES6 的范围,但是也被 babel 支持,普遍被大家接受和使用(需要安装插件)
  • 对象扩展符(Babel-plugin-transform-object-rest-spread 插件,支持扩展符号),函数绑定;
  • 装饰器;
  • async await。
3.4 其它的特性
  • promise;
  • 迭代器和生成器;
  • 代理 proxy。

三、express + mongodb 基础

  • express + mongodb 开发 web 后台接口;
  • express 开发 web 接口;
  • 非关系型数据库 mongodb(存储类似 json 的数据);
  • 使用 nodejs 的 mongoose 模块链接和操作 mongodb。

1. express

  • 基于 nodejs,快速、开发、极简的 web 开发框架;
  • 安装: npm install --save express
  • 创建服务器:
//新建一个文件 server.js
const express = require('express');

const app = express();

app.get('/',function(req,res){
  res.send('<h2>Hello World</h2>');
});
//返回 json 数据
app.get('/data',function(req,res){
  res.json({name: 'yjw',age: 18});
})
app.listen(3002, ()=>{
  console.log('监听到了端口 3002');
})
//在终端进入到 server.js 所在的文件夹,然后执行 node server.js 即可启动服务
//如果对文件中的内容进行修改,必须要先重新启动才能生效(ctrl + c,node server.js)
//如果想要修改 server.js 中的文件,不进行重启就使修改的内容生效,可使用插件 nodemon。首先全局安装 nodemon 插件;然后使用 nodemon server.js 进行启动;以后刷新浏览器页面即可。
  • 其它的特性:
    • app.get、app.post 分别开发 get 和 post 接口;
    • app.use 使用模块;
    • res.send(文本)、res.json(json)、res.sendfile(文件) 响应不同的内容。

2. mongodb + mongoose

  • 非关系型数据库
  • 官网 https://www.mongodb.com/ 下载安装 mongodb;
  • 安装 mongoose:npm install mongoose --save
  • 通过 mongoose 操作 mongodb,存储的就是 json,相对 mysql 来说,要易用很多。
2.1. mongoose 的基础使用
  • connect 链接数据库;
  • 定义文档模型,schema 和 model 新建模型;
  • String、Number 等数据结构;
  • create、remove、update 分别用来增、删、改的操作;
  • find 和 findOne 用来查询数据。
//找到 mongodb 安装目录,然后在控制台执行 mongo,即可连接上 MongoDB
const express = require('express');
const mongoose = require('mongoose');
// 链接 mongoose
const DB_URL = 'mongodb://127.0.0.1:27017';
mongoose.connect(DB_URL);
// 此句测试 是否链接成功,可以删除
mongoose.connection.on('connected',function(){
  console.log('mongoose 链接成功啦~')
})
// 类似于 mysql 的表,mongo 里面有文档、字段的概念
const User = mongoose.model('user',new mongoose.Schema({
  name:{type:String,require:true},
  age:{type:Number,require:true}
}))
//增加数据
// User.create({
//   name:'mike',
//   age:18
// },function(err,data){
//   if(!err){
//     console.log('增加数据成功:',data)
//   }else{
//     console.log('增加数据失败:',err)
//   }
// })
// 删除数据
// User.remove({name:'yijiang'},function(err,data){
//   console.log('删除数据成功:',data)
// })
// 更新数据,$set 可以省略
User.update({name:'mike'},{'$set':{age:28}},function(err,data){
  console.log('更新数据成功:',data)
})

// 新建app
const app = express();

app.get('/',function(req,res){
  res.send('<h2>Hello New App</h2>')
})

app.get('/data',function(req,res){
  // 查询数据
  User.find({name:'mike'},function(err,data){ //还有 findOne 方法,该方法返回一个 json
    res.json(data)
  })
})

app.listen(9003,function(){
  console.log('新建 APP')
})
2.2. express + mongodb 实战(后面讲解)
  • mongod --config /usr/local/etc/mongod.conf 后台启动;
  • express 结合 mongodb;
  • 封装 mongoose。
2.3. 后续进阶
  • express 和 mongodb 结合:
    • mongodb 独立工具函数;
    • express 使用 body-parser 支持 post 参数;
    • 使用 cookie-parser 存储登录信息 cookie。

四、React 基础简述

  • 这里只是简单回顾一下 propsstate生命周期函数的相关知识,详细学习请参考:React 之高级使用
  • 需要绑定 this 的三种解决方案:
    • ①在 constructor 方法中对方法进行绑定: this.aaa = this.aaa.bind(this)
    • ②调用函数时,使用箭头函数进行调用() => this.aaa()
    • ③定义函数时,使用箭头函数。
  • 安装 babel-plugin-import,进行按需加载:
    • 在 package.json 中的 plugins 按需加载:
  "babel": {
    "presets": [
      "react-app"
    ],
    "plugins": [
      ["import",{"libraryName": "antd-mobile","style": "css"}]
    ]
  }
  • 对 props、state 传值的简单使用:
import React,{Component} from 'react'
import {Button, List} from 'antd-mobile'
// import 'antd-mobile/dist/antd-mobile.css'

class App extends Component{
  render(){
    const director = '李云龙';
    return (
      <div>
        <h2>独立营营长:{director}</h2>
        <Yiying boss='yijiang'/>
        <Erying name='jiang' />
      </div> 
    )
  }
}

// 如果组件只有 render 函数,还可以用函数的形式写组件
function Erying(props){
  return <h2>二营营长:{props.name}</h2>
}

class Yiying extends Component{
  constructor(props){
    super(props);
    this.state = {soldiers:['虎子','狗子','嘎子']}
  }

  show(e){
    alert(e.target.innerText)
  }

  clickAntButton(e){
    alert(e.target.innerText)
  }

  render(){
    return (
      <div>
        <h2>一营营长:{this.props.boss}</h2>
        <Button type='primary' onClick={this.clickAntButton}>这里是ant Button </Button>
        <List renderHeader='士兵列表' renderFooter='收兵'>
          {this.state.soldiers.map((v,idx) => {
            return (
              <List.Item 
                extra='快来参军吧' 
                key={idx} 
                onClick={this.show}
                arrow='up'
              >士兵:{v}</List.Item>)
          })}
        </List>
      </div>
    )
  }
}

export default App
  • 生命周期函数执行顺序图:


    生命周期图.png

五、redux 的使用

1.redux基本概念

  • 是一个专注于状态管理的库:

    • redux 专注于状态管理,和 react 解耦,也可以和 vue、angular 结合使用;
    • 单一状态,单向数据流;
    • 核心概念:store、state、action、reducer。
  • redux 的结构可以模仿如下结构:


    redux 结构.png
  • redux 的使用方法:


    redux 的使用方法
  • 结合上图理解以下代码:

// src/index.js
import {createStore} from 'redux'
// 新建 store
// 通过 reducer 建立,reducer 根据老的状态和 action 生成新的 state
function counter(state=0,action){
  switch(action.type){
    case '加机关枪':
      return state + 1;
    case '减机关枪':
      return state -1;
    default:
      return 10;
  }
}

//通过 reducer 新建 store
const store = createStore(counter);
const init = store.getState();
console.log('初始化 state:',init); //初始化 state: 10

function listener(){
  const count = store.getState();
  console.log(`现有机枪 ${count} 把。`)
}
// 监听,每次 state 修改都会调用 listener
store.subscribe(listener)
// 现有机枪 11 把。
// 现有机枪 12 把。
// 现有机枪 11 把。

store.dispatch({type:'加机关枪'})
store.dispatch({type:'加机关枪'})
store.dispatch({type:'减机关枪'})
  • 以上代码中的 subscribe 方法可以进一步优化:
store.subscribe(()=>{
  console.log(`现有机枪 ${store.getState()} 把`)
})

2.redux 如何和 react 一起使用

2.1. 手动连接 react 和 redux
图片.png
  • 个人理解:dispatch(action)时,会自动将 action 传给 reducer,然后 reducer 进行相应处理;如果处理中 state 发生变化,会触发 store.subscribe(func)。
  • src 文件夹下有三个文件,内容分别如下:
// index.js
import React from 'react'
import {render} from 'react-dom'
import {createStore} from 'redux'

import App from './App'
import {reducer, addGun, removeGun} from './index.redux'

const store = createStore(reducer)

function show(){
  render(
    <App store={store} addGun={addGun} removeGun={removeGun}/>,
    document.getElementById('root')
  )
}

show()

store.subscribe(show)
// App.js
import React from 'react'
import {Button} from 'antd-mobile'

class App extends React.Component{
  render(){
    const {store, addGun, removeGun} = this.props
    return (
      <div>
        <h2>现有枪支:{store.getState()}</h2>
        <Button type='primary' inline onClick={()=>{store.dispatch(addGun())}}>增加枪支</Button>
        <Button type='primary' inline onClick={()=>store.dispatch(removeGun())}>减少枪支</Button>
      </div> 
    )
  }
}

export default App
// index.redux.js
const INCREASE_TYPE = 'INCREASE'
const REDUCE_TYPE = 'REDUCE'

export function addGun(){
  return {type:INCREASE_TYPE}
}
export function removeGun(){
  return {type:REDUCE_TYPE}
}

export function reducer(state=0,action){
  switch (action.type){
    case 'INCREASE':
      return state + 1
    case 'REDUCE':
      return state -1
    default:
      return 10
  }
}
2.2. 处理异步、调试工具、更优雅的将 react 和 redux 结合:
  • redux 异步处理,需要使用 redux-thunk 插件
  • 调试工具:npm install redux-devtools-extension 并且开启
  • 使用 react-redux 优雅的连接 react 和 redux
2.2.1. redux异步处理:

redux 默认处理同步,异步任务需要 redux-thunk 中间件:

  • npm i --save redux-thunk
  • 使用 applyMiddleware 开启 thunk 中间件(见下面代码)
  • action 可以返回函数,使用 dispatch 提交 action
2.2.2. 调试工具
  • 亲测发现,只要在 Chrome 浏览器中安装插件 Redux DevTools
  • 然后再从 redux 中引入 compose
    • 将原来创建 store 的方式修改一下:
//原来创建 store 的方式
const store = createStore(reducer,applyMiddleware(thunk))
// compose 将函数组合到一起 , 在 Chrome 中安装插件 redux 后,将上面创建 store 方式改为如下形式,在浏览器中调试清晰方便
// 现在创建 store 的方式
const store = createStore(reducer,compose(
  applyMiddleware(thunk),
  window.devToolsExtension ? window.devToolsExtension() : ()=>{}
))
  • 现在 src 文件夹下的代码如下:
// index.js
import React from 'react'
import {render} from 'react-dom'
import {createStore, applyMiddleware, compose} from 'redux'
import thunk from 'redux-thunk'

import App from './App'
import {reducer, addGun, removeGun, addGunAsync} from './index.redux'

// const store = createStore(reducer,applyMiddleware(thunk))
// compose 将函数组合到一起 , 在 Chrome 中安装插件 redux 后,将上面创建 store 方式改为如下形式,在浏览器中调试清晰方便
const store = createStore(reducer,compose(
  applyMiddleware(thunk),
  window.devToolsExtension ? window.devToolsExtension() : ()=>{}
))

function show(){
  render(
    <App store={store} addGun={addGun} removeGun={removeGun} addGunAsync={addGunAsync}/>,
    document.getElementById('root')
  )
}

show()

store.subscribe(show)
// App.js
import React from 'react'
import {Button} from 'antd-mobile'

class App extends React.Component{
  render(){
    const {store, addGun, removeGun, addGunAsync} = this.props
    return (
      <div>
        <h2>现有枪支:{store.getState()}</h2>
        <Button type='primary' inline onClick={()=>{store.dispatch(addGun())}}>增加枪支</Button>
        <Button type='primary' inline onClick={()=>{store.dispatch(addGunAsync())}}>迟缓加枪</Button>
        <Button type='primary' inline onClick={()=>store.dispatch(removeGun())}>减少枪支</Button>
      </div> 
    )
  }
}

export default App
// index.redux.js
const INCREASE_TYPE = 'INCREASE'
const REDUCE_TYPE = 'REDUCE'

// action creator
export function addGun(){
  return {type:INCREASE_TYPE}
}
export function removeGun(){
  return {type:REDUCE_TYPE}
} 
//异步
export function addGunAsync(){
  // thunk 插件的作用,这里可以返回函数
  return dispatch => {
    setTimeout(()=>{
      dispatch(addGun())
    },2000)
  }
}

export function reducer(state=0,action){
  switch (action.type){
    case 'INCREASE':
      return state + 1
    case 'REDUCE':
      return state -1
    default:
      return 10
  }
}
2.2.3. 使用 react-redux
  • 首先安装插件: npm install --save react-redux
  • 忘记 subscribe,记住 reducer,action 和dispatch 即可
  • react-redux 提供 Provider 和 connect 两个接口来连接
  • 具体使用:
    • Provider 组件在应用最外层,传入 store 即可,只用一次
    • connect 负责在组件内从外部获取组件需要的参数,这时组件内不需要显式触发事件(dispatch),直接传 actionCreator 即可
    • connect 可以用装饰器的方式来写
  • src 文件夹下内容:
// index.js
import React from 'react'
import {render} from 'react-dom'
import {createStore, applyMiddleware, compose} from 'redux'
import thunk from 'redux-thunk'
import {Provider} from 'react-redux'

import App from './App'
import {reducer} from './index.redux'

const store = createStore(reducer,compose(
  applyMiddleware(thunk),
  window.devToolsExtension ? window.devToolsExtension() : ()=>{}
))

render(
  (
    <Provider store={store}>
      <App />
    </Provider>
  ),
  document.getElementById('root')
)
// App.js
import React from 'react'
import {Button} from 'antd-mobile'
import {connect} from 'react-redux'

import {addGun, removeGun, addGunAsync} from './index.redux'

class App extends React.Component{
  render(){
    const {num, addGun, removeGun, addGunAsync} = this.props;
    return (
      <div>
        <h2>现有枪支:{num} 支</h2>
        <Button type='primary' inline onClick={addGun}>增加枪支</Button>
        <Button type='primary' inline onClick={addGunAsync}>迟缓加枪</Button>
        <Button type='primary' inline onClick={removeGun}>减少枪支</Button>
      </div> 
    )
  }
}

const mapStateToProps = state => {
  return {num:state}
}
const actionCreators = {addGun, removeGun, addGunAsync}
App = connect(mapStateToProps,actionCreators)(App)

export default App
// index.redux.js
const INCREASE_TYPE = 'INCREASE'
const REDUCE_TYPE = 'REDUCE'

// action creator
export function addGun(){
  return {type:INCREASE_TYPE}
}
export function removeGun(){
  return {type:REDUCE_TYPE}
} 
//异步
export function addGunAsync(){
  // thunk 插件的作用,这里可以返回函数
  return dispatch => {
    setTimeout(()=>{
      dispatch(addGun())
    },2000)
  }
}

export function reducer(state=0,action){
  switch (action.type){
    case 'INCREASE':
      return state + 1
    case 'REDUCE':
      return state -1
    default:
      return 10
  }
}
  • 使用 装饰器优化 connect 代码:
    • 弹出个性化配置:npm run eject
    • 安装插件:npm i babel-plugin-transform-decorators-legacy
    • package.json 里 babel 加上 plugins 配置:
"babel": {
    "presets": [
      "react-app"
    ],
    "plugins": [
      [
        "import",
        {
          "libraryName": "antd-mobile",
          "style": "css"
        }
      ],
      "transform-decorators-legacy"
    ]
  },
  • 使用装饰器,主要是对 connect 进行优化:
// App.js
import React from 'react'
import {Button} from 'antd-mobile'
import {connect} from 'react-redux'

import {addGun, removeGun, addGunAsync} from './index.redux'

// const mapStateToProps = state => {
//   return {num:state}
// }
// const actionCreators = {addGun, removeGun, addGunAsync}
// App = connect(mapStateToProps,actionCreators)(App)
@connect(
  state => ({num:state}),
  {addGun, removeGun, addGunAsync}
)
class App extends React.Component{
  render(){
    const {num, addGun, removeGun, addGunAsync} = this.props;
    return (
      <div>
        <h2>现有枪支:{num} 支</h2>
        <Button type='primary' inline onClick={addGun}>增加枪支</Button>
        <Button type='primary' inline onClick={addGunAsync}>迟缓加枪</Button>
        <Button type='primary' inline onClick={removeGun}>减少枪支</Button>
      </div> 
    )
  }
}

export default App

六、react-router4

  • React 官方推荐的路由库,4 是最新版本
    • 4 是全新的版本,和之前版本不兼容,浏览器和 RN 均兼容;
    • React 开发单页应用必备,践行路由即组件的概念;
    • 核心概念:动态路由、Route、Link、Switch。

1、初识 router4

  • 安装: npm i --save react-router-dom
  • router4 使用 react-router-dom 作为浏览器端的路由
  • 忘掉 router2 的内容
1.1、入门组件
  • BrowserRouter,包裹整个应用;
  • Route 路由对应渲染的组件,可以嵌套,语法:<Route path='/path' component={要渲染的组件}></Route>
  • Link 跳转专用,语法:<Link to='/path'>展示的名称</Link>
  • 示例:这里只在 index.js 文件中进行添加
// index.js
import React from 'react'
import {render} from 'react-dom'
import {createStore, applyMiddleware, compose} from 'redux'
import thunk from 'redux-thunk'
import {Provider} from 'react-redux'
import {BrowserRouter, Route, Link} from 'react-router-dom'
import App from './App'
import {reducer} from './index.redux'

const store = createStore(reducer,compose(
  applyMiddleware(thunk),
  window.devToolsExtension ? window.devToolsExtension() : ()=>{}
))

function Erying(){
  return <h2>欢迎来到二营</h2> 
}
class Qibinglian extends React.Component{
  render(){
    return <h2>这里是骑兵连</h2>
  }
}

render(
  <Provider store={store}>
    <BrowserRouter>
      <div>
        <ul>
          <li><Link to='/'>一营</Link></li>
          <li><Link to='/erying'>二营</Link></li>
          <li><Link to='/qibinglian'>骑兵连</Link></li>
        </ul>
        <Route path='/' exact component={App}></Route>
        <Route path='/erying' component={Erying}></Route>
        <Route path='/qibinglian' component={Qibinglian}></Route>
      </div>
    </BrowserRouter>
  </Provider>
  ,
  document.getElementById('root')
)
1.2、其它组件
  • url 参数:Route 组件参数可用冒号标识参数;
class Test extends React.Component{
  render(){
    console.log(this.props.match.params.location)
    return <h2>测试组件</h2> 
  }
}
===============================
<ul>
  <li><Link to='/'>首页</Link></li>
  <li><Link to='/erying'>二营</Link></li>
  <li><Link to='/qibinglian'>骑兵连</Link></li>
</ul>

<Route path='/' exact component={App}></Route>
<Route path='/:location' exact component={Test}></Route>

点击“二营”、“骑兵连”会显示 Test 组件,并且可以打印相关的信息,这里会显示:“erying”、“qibinglian”。

  • Redirect 组件:跳转;
  • Switch 只渲染一个子 Route 组件。
// index.js
import React from 'react'
import {render} from 'react-dom'
import {createStore, applyMiddleware, compose} from 'redux'
import thunk from 'redux-thunk'
import {Provider} from 'react-redux'
import {BrowserRouter, Route, Link, Redirect,Switch} from 'react-router-dom'
import App from './App'
import {reducer} from './index.redux'

const store = createStore(reducer,compose(
  applyMiddleware(thunk),
  window.devToolsExtension ? window.devToolsExtension() : ()=>{}
))

class Erying extends React.Component{
  render(){
    return <h2>二营</h2>
  }
}

function Qibinglian(){
  return <h2>骑兵连</h2>
}

class Test extends React.Component{
  render(){
    console.log(this.props.match.params.location)

    return <h2>这里应该是 404 页面哟~ <br/>没有{this.props.match.params.location}这个路径喔~</h2> 
  }
}

render(
  <Provider store={store}>
    <BrowserRouter>
      <div>
        <ul>
          <li><Link to='/'>首页</Link></li>
          <li><Link to='/erying'>二营</Link></li>
          <li><Link to='/qibinglian'>骑兵连</Link></li>
        </ul>

        {/* <Redirect to='/qibinglian' component={Qibinglian}></Redirect> */}
        <Switch>
          {/* Switch 里面只渲染命中的第一个 Route */}
          <Route path='/' exact component={App}></Route>
          <Route path='/erying' component={Erying}></Route>
          <Route path='/qibinglian' component={Qibinglian}></Route>
          <Route path='/:location' component={Test}></Route>
        </Switch>
      </div>
    </BrowserRouter>
  </Provider>
  ,
  document.getElementById('root')
)
  • 复杂的 redux 应用,会遇到多个 reducer 的情况,用 combineReducers 进行合并。

七、项目开发

  • 这里不再贴详细代码,只列出用到的对应的知识点。详细代码可参照项目地址:项目地址

1、前后端联调

  • 使用 axios 发送异步请求:
    • 如何发送,端口不一致时,使用 proxy 配置转发;
    • axios 拦截器,统一 loading 处理;
    • redux 里使用异步数据,渲染页面;
    • 拦截器:interceptor。

2、登录注册功能的实现

2.1、 页面文件结构;
  • 骨架结构实现:
    • 组件放在 Component 文件夹下面;
    • 页面放在 Container 文件夹下面;
    • 页面入口处获取用户信息,决定跳转到哪个页面。
2.2、web 开发模式;
  • 基于 cookie 用户验证:
    • express 依赖 cookie-parser,需要安装:npm i cookie-parser --save
    • cookie 类似于一张身份卡,登陆后服务器端返回,你带着 cookie 就可以访问受限资源;
    • cookie 的管理:浏览器会自动处理。
  • 开发流程:


    开发流程
2.3、前后端实现。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,425评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,058评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,186评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,848评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,249评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,554评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,830评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,536评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,239评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,505评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,004评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,346评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,999评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,060评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,821评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,574评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,480评论 2 267

推荐阅读更多精彩内容