react_新手入门教程05——react + express + mongoose 实现CURD


上节用纯前端的方式,实现CURD,
这节从之前的基础上,做些修改,完成react 与后端接口的交互

注: 原本想用egg的:考虑大家用的express比较多,就换成express了

这节用到的的技术

  • [x] promise
  • [x] async await
  • [x] mongodb
  • [x] mongoose
  • [x] mongoose-auto-increment

整个项目结构

├── models(mongoos.js model,宗旨上以数据表名一一对应)
├── routes(接口)
├── service (服务层,一般用于需要封装的独立服务,比如db)
├── src (前端工程)
│   |── src (前端源码)
│   |── components (公用自定义组件,以文件夹为单位)
│   |── img (图片)
│   |── pages (页面级别组件,以文件夹为单位)
│   |── service (前端的Ajax请求函数封装)
│   |── style (核心样式表-总)
│   |── tools (前端工具函数)
│   |── index.js (入口)
│   |── router.js (前端路由表)
│   |── README.md 
├── app.js (入口)
├── README.md

后端

安装expresss

此处省略。。。

mac下安装mongodb

  • brew install mongodb
  • brew cask install launchrocket
  • 打开launchrocket start mongodb

安装 mongoose

$ npm install mongoose
  • 在后端service文件夹中新建db.js
  • mongoose初始化
/**
 * mongoose初始化配置
 */

const mongoose = require('mongoose'),
    DB_URL = 'mongodb://localhost:27017/test',
    autoIncrement = require('mongoose-auto-increment');
/**
 * 连接
 */
const connection = mongoose.createConnection(DB_URL);

/**
 * id自增插件初始化
 */
autoIncrement.initialize(connection);

/**
  * 连接成功
  */
mongoose.connection.on('connected', function () {
    console.log('Mongoose connection open to ' + DB_URL);
});

/**
 * 连接异常
 */
mongoose.connection.on('error',function (err) {
    console.log('Mongoose connection error: ' + err);
});

/**
 * 连接断开
 */
mongoose.connection.on('disconnected', function () {
    console.log('Mongoose connection disconnected');
});

module.exports = {
  mongoose:mongoose,
  connection:connection,
  autoIncrement:autoIncrement,
};
  • 后端modals文件夹中新建all.js
  • 在all.js 定义 数据结构操作数据库的model
/**
 * 定义 数据结构 及 操作数据库的model
 */

const db = require('../service/db.js'),
    Schema = db.mongoose.Schema;

/**
 * 定义 modal数据结构
 */
const allSchema = new Schema({
      name: {
        type: String
      },                       //名字
      age: {
        type: String
      },                       //年龄
      address: {
        type: String
      }                        //地址
    }, {
      versionKey: false        // 版本号不显示
    })


//创建其他modal只需改下 model名 和 数据结构
const modalName = "all"

/**
 * id自增插件引入  设置从1开始自增
 */
allSchema.plugin(db.autoIncrement.plugin, { model: modalName, field: 'id', startAt:1 });

module.exports = db.connection.model(modalName, allSchema);

其中要提一点的是,mongodb本身无id自增功能,
所以应用了插件 mongoose-auto-increment实现

之所以需要id自增,是因为后端主要通过id判断来做增删改查,
我比较习惯这样,你也可以用其他方案 :)

关于mongoose的细节不赘述;
Mongoose介绍和入门:http://www.cnblogs.com/zhongweiv/p/mongoose.html
mongoose-auto-increment https://www.npmjs.com/package/mongoose-auto-increment

启动后端

node app.js

增(create)删(delete)改(update)查(select)

之前 前端做增删改, 现在这块逻辑放在后端
和之前的逻辑无差, 主要判断对象中的id, status
若无id, 则新建
有id,status为0, 则修改
有id,status为-1,则删除

//routes/all.js

//select
main.get('/list', async (request, response) => {

  let ret = {
  "success": true,
  "code": 200,
  "message": "",
  "data": [],
  }

  const datas = await Model.find()
  ret.data = datas
  response.send(ret)
})


//create, delete, update
main.post('/update', async (request, response) => {

  let ret = {
  "success": true,
  "code": 200,
  "message": "",
  "data": [],
  }
  
  const body = request.body,
          id = body.id || 0,
          status = body.status || 0

  const args = body

  if (!id) {
    //新建
    const dataSourceObj = await Model.create(args)

    ret.data = {
      id: dataSourceObj.id, create:true
    }

  }
  else if (!status) {
    //修改
    const dataSourceObj = await Model.findOne({id: args.id})

    for ( let key in args) {
      if(key =='_id' || key =='id' ) {
        continue
      }
      dataSourceObj[key]= args[key]
    }

    const new_dataSourceObj = await dataSourceObj.save()

    ret.data = {
      id: new_dataSourceObj.id, update:true
    }

  } else {
    //删除
    const dataSourceObj = await Model.findOne({id: args.id})

    const remove = await dataSourceObj.remove()

    ret.data = {
      id: dataSourceObj.id, delete:true
    }

  }
  
  response.send(ret)
})

ok 这是接口逻辑,实际功能已经实现,
但未做接口防护,这点下节再写吧

前端

前端在tools引入封装的ajx工具

//src/tools/ajax.js


const joinQuery = function(params) {

  var Querys = Object.keys(params).map( key => {
    return `${key}=${params[key]}`
  }).join('&')

  return `?${Querys}`
}

//原生ajx
const ajax = function(request) {

    var r = new XMLHttpRequest()
    r.open(request.method, request.url, true)
    if (request.contentType !== undefined) {
        r.setRequestHeader('Content-Type', request.contentType)
    }
    r.onreadystatechange = function(event) {
        if(r.readyState === 4) {
            const data = JSON.parse(r.response)
            request.success(data)
        }
    }
    if (request.method === 'GET') {
        r.send()
    } else {
        r.send(request.data)
    }
}

//用Promise封装原生ajx
const ajaxPromise = function(url, method, form) {
    var p = new Promise((resolve, reject) => {
        const request = {
            url: url,
            method: method,
            contentType: 'application/json',
            success: function(r) {
                resolve(r)
            },
            error: function(e) {
                const r = {
                    success: false,
                    message: '网络错误, 请重新尝试',
                }
                //promise失败扔出错误
                reject(r)
            },
        }
        if (method === 'post') {
            const data = JSON.stringify(form)
            request.data = data
        }
        ajax(request)
    })
    return p
}

//封装 ajaxPromise
const _ajax = {
    get: (path, params={}) => {
        const url = path + joinQuery(params)
        const method = 'get'
        const form = {}
        return ajaxPromise(url, method, form)
    },
    post: (path, params={})=>{
        const url = path
        const method = 'post'
        return ajaxPromise(url, method, params)
    },
}

module.exports = _ajax

在src/service中新建all组件的ajax请求,方便all组件调用

//src/service/all.js

import ajax from '../tools/ajax'



//组件请求类

const All = {

    getList:(params) => {
      let data = ajax.get('/all/list',  params )
            .then((response) => {
              return response
            })
      return data
    },

    update: (params) => {
      let data = ajax.post('/all/update',  params )
            .then((response) => {
              return response
            })
      return data
    }

}

export default All

后端安装及接口逻辑和前端ajx工具都引入完成!

修改前端逻辑

之前的id是前端生成的
现在是后端提供,所以修改key为id

修改:
- key改为id
- saveData()方法删除,新建和修改统一用updateDataHandle()方法

//src/pages/all/edit/index.js

class EditModel extends Component {
  constructor(props) {
    super(props);
    this.state = {
—      key:0,
    }
  }




onOk = () => {
    const { editDataObj, updateDataHandle, onModelCancel, saveData} = this.props
    //getFieldsValue() 获取表单中输入的值
    const { getFieldsValue, resetFields } = this.props.form
    const values = getFieldsValue()
    //antd table需要加一个key字段

    //判断是更新 还是添加
+    if(editDataObj.id) {
      //输入框本身无key
+      values.id = editDataObj.id
_      // //调用父组件方法改变dataSourse
_      // updateDataHandle(values)
    }
_    // else {
_    //   const key = this.state.key + 1
_    //   this.setState({
_    //     key:key,
_    //   })
_    //   values.key = key
_    //   saveData(values)
_    // }
    updateDataHandle(values)
    //重置表单
    resetFields()
    onModelCancel()
  }
//src/pages/all/index.js
 
 
-  //储存数据
-  saveData = (updateData) => {
-
-    const { dataSource } = this.state
-    dataSource.push(updateData)

-    this.setState({
-      dataSource:dataSource,
-    })
-  }





  //修改
  updateDataHandle = async (values)=> {
-   //  const { dataSource } = this.state
-   //  const id = values.key,
-   //          status = values.status || 0
-   //
-   //  const index = dataSource.findIndex(e=> e.key == id)
-   //  //替换
-   // if(status >= 0) {
-   //   let replace = dataSource.splice(index,1,values)
-   // } else {
-   //   //删除
-   //   let removed = dataSource.splice(index,1)
-   // }

+   const { data } = await AllService.update(values)

-    // this.setState({
-    //   dataSource:data,
-    // })
  }
  

我们来新建一个数据试试

此处输入图片的描述
此处输入图片的描述

发现已经有http请求了,不过报错了

这是http协议同源策略限制导致的,也就是俗称的端口跨域
这里 create-react-app 已经提过了一个简单的方法
在src/package.json中加一句 "proxy": "http://localhost:8000"

{
  "name": "public",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "antd": "^3.2.2",
    "react": "^16.2.0",
    "react-dom": "^16.2.0",
    "react-router-dom": "^4.2.2",
    "react-scripts": "1.1.1"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test --env=jsdom",
    "eject": "react-scripts eject"
  },
+  "proxy": "http://localhost:8000"
}

配置完后,记得重启下前端 yarn start

再新建一条数据可以看到, 新建成功


此处输入图片的描述
此处输入图片的描述

但数据并未渲染在table上, 所添加一个请求列表数据的方法

//src/pages/all/index.js


+  //请求列表数据
+  getDataSourseList = async () => {

+    const { data } = await AllService.getList()
+    this.setState({
+      dataSource:data,
+    })
+  }


  //修改
  updateDataHandle = async (values)=> {
  
   const { data } = await AllService.update(values)
+  this.getDataSourseList()
  }

  

数据新建数据就有了


此处输入图片的描述
此处输入图片的描述

现在还有个问题: 刷新路由后,数据未渲染在table上
所以这里需要加个reactd的钩子:componentWillMount()

componentWillMount会在组件render之前执行

react生命周期:https://hulufei.gitbooks.io/react-tutorial/content/component-lifecycle.html

//src/pages/all/index.js

+//componentWillMount会在组件render之前执行
+ componentWillMount() {
+    this.getDataSourseList()
+  }

最后修复剩下的几个bug

  • fix: 删除
  • fix: id不显示
//删除
  deleteHandle = (record) => {
    confirm({
-      title: `您确定要删除?(${record.key})`,
+      title: `您确定要删除?(${record.id})`,
      onOk: () => {
        this.updateDataHandle({
-         key:record.key,
+          id:record.id,
          status:-1,
        })
      },
    });
  }
  
render() {
    // editVisiable控制弹窗显示, dataSource为tabale渲染的数据
    //
    const { editVisiable, dataSource, editDataObj} = this.state

+    //数据加个key 喂antd
+    dataSource.map((e,index)=> e.key = index+1)
    
    return (
      <div className="content-inner">
        <Button type ='primary' onClick={ this.addDataSource }> 新建数据</Button>
        <Table
        columns = {this.columns}
        dataSource={dataSource}
        />
        <EditModal
        editVisiable={ editVisiable }
        onModelCancel={ this.onModelCancel}
        saveData = { this.saveData }
        editDataObj = { editDataObj }
        updateDataHandle = { this.updateDataHandle }
        />
      </div>
    );
  }


  //定义表格
  columns = [{
    title: 'id',
-    dataIndex: 'key',
-    key: 'key',
+    dataIndex: 'id',
+    key: 'id',
  },{
    title: '姓名',
    dataIndex: 'name',
    key: 'name',
  }, {
    title: '年龄',
    dataIndex: 'age',
    key: 'age',
  }, {
    title: '住址',
    dataIndex: 'address',
    key: 'address',
  }, {
          title: '操作',
          dataIndex: 'operation',
          key: 'operation',
          render: (text, record) => (
              <div style={{ textAlign: 'ceter' }}>
                  <a href="javascript:void(0)" style={{ marginRight: '10px' }}
              onClick={() => this.editHandle(record)}
            >编辑</a>
            <a href="javascript:void(0)" style={{ marginRight: '10px' }}
              onClick={() => this.deleteHandle(record)}
            >删除</a>
                </div>
            ),
        }];  
  
  

github地址:https://github.com/hulubo/react-express-mongoose-CURD-demo
其中前端的包和后端的包应该放一起的,先这样吧,到时候改

(完...)

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

推荐阅读更多精彩内容