react_新手入門教程05——react + express + mongoose 實現CURD

weixin_33935777發表於2018-03-14

上節用純前端的方式,實現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
其中前端的包和後端的包應該放一起的,先這樣吧,到時候改

(完...)

相關文章