您的位置:首頁>正文

Node.js + React + MongoDB 實現 TodoList 單頁應用

之前用 Ant Design 開發了一個專案, 因此對 React 的特性有了一定的瞭解, React 使用封裝元件的思想, 元件各自維護自己的狀態和 UI, 元件之間通過 props 傳遞資料和方法。 當狀態更新時自動重繪整個元件, 從而達到局部刷新的效果, 大大提高了 DOM 更新的效率, 同時元件化十分有利於維護。 在對 React 進行進一步的學習後, 使用 Node.js + React 的方式實現了一個簡單的 TodoList 單頁應用, 同時涉及簡單的 MongoDB 資料庫操作, 總的來說, 專案相對簡單, 十分適合 React 的入門學習。

應用功能

1、添加 todoList

2、刪除 todoList

應用效果圖

專案運行環境:

Windows/Mac

Node.js v6.9.4 or later

MongoDB

安裝和配置 MongoDB:

Mac:http://www.cnblogs.com/wx1993/p/5187530.html

Windows:http://www.cnblogs.com/wx1993/p/5206587.html

http://www.cnblogs.com/wx1993/p/6518248.html

專案初始化

創建node專案(已經安裝 Node.js, express, express-generator)

express -e demo

生成的檔目錄結構如下:

配置 package.json

打開 package.json 檔, 配置好專案需要安裝的依賴如下:

1 { 2 "name": "demo", 3 "version": "0.0.0", 4 "private": true, 5 "scripts": { 6 "start": "node ./bin/www" 7 }, 8 "dependencies": { 9 "body-parser": "~1.16.0", 10 "cookie-parser": "~1.4.3", 11 "debug": "~2.6.0", 12 "ejs": "~2.5.5", 13 "express": "~4.14.1", 14 "jquery": "^3.1.1", 15 "mongoose": "^4.8.6", 16 "morgan": "~1.7.0", 17 "serve-favicon": "~2.3.2" 18 }, 19 "devDependencies": { 20 "babel": "^6.23.0", 21 "babel-cli": "^6.23.0", 22 "babel-core": "^6.23.1", 23 "babel-loader": "^6.4.0", 24 "babel-preset-es2015": "^6.22.0", 25 "babel-preset-react": "^6.23.0", 26 "jquery": "^3.1.1", 27 "react": "^15.4.2", 28 "react-dom": "^15.4.2", 29 "webpack": "^2.2.1" 30 } 31 }

安裝依賴:

npm install

安裝 react、react-dom、webpack

npm install react react-dom webpack

Webpack 配置

在 node 專案下新建 webpack.config.js 檔, 因為項目使用的技術方案為 webpack + react + es6, 因此在 webpack 中配置如下:

1 var path = require("path"); 2 3 module.exports={ 4 // 項目入口 5 entry: "./src/pages/app.js", 6 // 打包檔輸出路徑 7 output: { 8 path: path.join(__dirname,"./public/js"), 9 filename: "bundle.js", 10 }, 11 module: { 12 loaders: [{ 13 test: /.js$/, 14 loader: "babel-loader", 15 query: { 16 presets: ['react','es2015'] 17 } 18 },{ 19 test: /.jsx$/, 20 loader: 'babel-loader', 21 query: { 22 presets: ['react', 'es2015'] 23 } 24 },{ 25 test: /.css$/, 26 loader: "style!css" 27 },{ 28 test: /.(jpg|png|otf)$/, 29 loader: "url?limit=8192" 30 },{ 31 test: /.scss$/, 32 loader: "style!css!sass" 33 }] 34 } 35 };

修改 app.js, 連接資料庫

打開專案中的 app.js 檔, 添加代碼:

var mongoose = require('mongoose') mongoose.connect('mongodb://localhost:27017/todo')

使用 node.js 的 mongoose 庫方法連接 MongoDB 資料庫, 27017 是資料庫預設埠號, todo是資料庫名稱, 可自訂。

啟動 MongoDB 服務

在命令列視窗輸入命令

mongod --dbpath D:mongodb/data

dbpath 後面的是 MongoDB 下 data 資料夾所在目錄, 結果如下:

啟動專案

npm start

打開流覽器視窗, 效果如下:

那麼到這裡, 項目基本上就跑起來了(暫時沒有使用到webpack)

接下來看一下專案的目錄結構:

src 下主要存放元件檔和資料庫相關檔public 下是靜態檔和打包後的 js 文件router 下 index.js 定義了頁面路由和封裝了資料庫操作的介面views 下 index.ejs 是專案的入口頁面app.js 是 Node.js 服務的入口檔, 在這裡連接 MongoDB 資料庫webpack.config.js 定義了專案的入口和輸出檔和路徑以及各種載入器 loader

首先看入口頁面 index.ejs

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

入口文件 src/pages/app.js

1 import React from 'react' 2 import ReactDOM from 'react-dom' 3 import Todo from './index.js' 4 5 ReactDOM.render( 6 , 7 document.getElementById("app") 8 );

webpack會將入口檔進行合併和整理, 最後輸出一個bundle.js, 所以所有的邏輯都在這個js檔中, 因此在index.html中, 只需要引入react框架和bundle.js就可以了。

接下來是資料庫的定義和操作

src/schemas/todo.js

1 var mongoose = require('mongoose'); 2 var Schema = mongoose.Schema; 3 4 var Todo = new Schema({ 5 content: { 6 type: String, 7 required: true 8 }, 9 date: { 10 type: String, 11 required: true 12 } 13 }, { collection: 'todo' }); 14 15 module.exports = Todo;

資料集合十分簡單, 兩個欄位, 內容和時間, 並保存在 todo 表中, 然後在 model 下的 todo.js 中定義資料庫模型:

var mongoose = require('mongoose'); var TodoSchema = require('../schemas/todo'); var TodoBox = mongoose.model('TodoBox', TodoSchema); module.exports = TodoBox;

在路由中封裝資料庫操作介面, 如下:

routes/index.js

1 var express = require('express'); 2 var router = express.Router; 3 var Todo = require('../src/models/todo') 4 5 router.get('/', (req, res, next) => { 6 res.render('index', { 7 title: 'React TodoList' 8 }); 9 }); 10 11 // 獲取全部的todo 12 router.get('/getAllItems', (req, res, next) => { 13 Todo.find({}).sort({'date': -1}).exec((err, todoList) => { 14 if (err) { 15 console.log(err); 16 }else { 17 res.json(todoList); 18 } 19 }) 20 }); 21 22 // 添加todo 23 router.post('/addItem', (req, res, next) => { 24 let newItem = req.body; 25 Todo.create(newItem, (err) => { 26 if (err) { 27 console.log(err); 28 }else { 29 Todo.find({}, (err, todoList) => { 30 if (err) { 31 console.log(err); 32 }else { 33 res.json(todoList); 34 } 35 }); 36 } 37 }) 38 }) 39 40 // 刪除todo 41 router.post('/deleteItem', (req, res, next) => { 42 console.log(req.body); 43 let delete_date = req.body.date 44 Todo.remove({date: delete_date}, (err, result) => { 45 if (err) { 46 console.log(err) 47 }else { 48 res.json(result); 49 } 50 }); 51 }); 52 53 module.exports = router;

代碼也相對簡單, 主要是資料的增刪改查。 封裝好介面之後, 在元件中就可以通過 ajax 進行請求來完成資料的操作。

下面來分析一下元件。

根據專案的功能分成了三個元件, 分別是父元件 index, todo清單子組件 todo-list, todo清單子組件 todo-item。

父組件 index.js

1 import React, { Component, PropTypes } from 'react' 2 import ReactDOM from 'react-dom' 3 import $ from 'jquery' 4 import TodoList from './comps/todo-list' 5 6 class Todo extends React.Component { 7 8 constructor(props) { 9 super(props); 10 this.state = { 11 todoList: , 12 showTooltip: false // 控制 tooltip 的顯示隱藏 13 } 14 } 15 16 componentDidMount { 17 // 獲取所有的 todolist 18 this._getTodoList; 19 } 20 21 // 獲取 todolist 22 _getTodoList { 23 const that = this; 24 $.ajax({ 25 url: '/getAllItems', 26 type: 'get', 27 dataType: 'json', 28 success: data => { 29 const todoList = that.todoSort(data) 30 that.setState({ 31 todoList 32 }); 33 }, 34 error: err => { 35 console.log(err); 36 } 37 }); 38 } 39 40 // 添加 todo 41 _onNewItem (newItem) { 42 const that = this; 43 $.ajax({ 44 url: '/addItem', 45 type: 'post', 46 dataType: 'json', 47 data: newItem, 48 success: data => { 49 const todoList = that.todoSort(data); 50 that.setState({ 51 todoList 52 }); 53 }, 54 error: err => { 55 console.log(err); 56 } 57 }) 58 } 59 60 // 刪除 todo 61 _onDeleteItem (date) { 62 const that = this; 63 const postData = { 64 date: date 65 }; 66 $.ajax({ 67 url: '/deleteItem', 68 type: 'post', 69 dataType: 'json', 70 data: postData, 71 success: data => { 72 this._getTodoList; 73 }, 74 error: err => { 75 console.log(err); 76 } 77 }) 78 } 79 80 // 對 todolist 進行逆向排序(使新錄入的專案顯示在清單上面) 81 todoSort (todoList) { 82 todoList.reverse; 83 return todoList; 84 } 85 86 // 提交表單操作 87 handleSubmit(event){ 88 89 event.preventDefault; 90 // 表單輸入為空驗證 91 if(this.refs.content.value == "") { 92 this.refs.content.focus; 93 this.setState({ 94 showTooltip: true 95 }); 96 return ; 97 } 98 // 生成參數 99 var newItem={ 100 content: this.refs.content.value, 101 date: (new Date.getMonth +1 ) + "/" 102 + new Date.getDate + " " 103 + new Date.getHours + ":" 104 + new Date.getMinutes + ":" 105 + new Date.getSeconds 106 }; 107 // 添加 todo 108 this._onNewItem(newItem) 109 // 重置表單 110 this.refs.todoForm.reset; 111 // 隱藏提示資訊 112 this.setState({ 113 showTooltip: false, 114 }); 115 } 116 117 render { 118 return ( 119 120 Todo List 121 122 123 { this.state.showTooltip && 124 Content is required ! 125 } 126 127 128 129 ) 130 } 131 } 132 133 export default Todo;

父組件的功能:

1、在元件 DidMounted 時通過 ajax 請求所有的資料與 state 綁定實現首次渲染;

2、將資料, 相應的方法分發給個子元件;

3 、實現添加、刪除方法並傳遞給子元件。 添加筆記的方法被觸發的時候, 發送ajax請求實現資料庫資料的更新,再更新元件的state使之資料與後臺資料保持一致,state一更新視圖也會被重新渲染實現無刷新更新。

子組件 todo-list

1 import React from 'react'; 2 import TodoItem from './todo-item'; 3 4 class TodoList extends React.Component { 5 6 render { 7 // 獲取從父組件傳遞過來的 todolist 8 const todoList = this.props.todoList; 9 // 迴圈生成每一條 todoItem,並將 delete 方法傳遞給子元件 10 const todoItems = todoList.map((item,index) => { 11 return ( 12 18 ) 19 }); 20 21 return ( 22 23 { todoItems } 24 25 ) 26 } 27 } 28 29 export default TodoList;

子組件 todo-item

1 import React from 'react'; 2 3 class TodoItem extends React.Component { 4 5 constructor(props) { 6 super(props); 7 this.state = { 8 showDel: false // 控制刪除 icon 的顯示隱藏 9 } 10 } 11 12 handleDelete { 13 // 獲取父組件傳遞過來的 date 14 const date = this.props.date; 15 // 執行父元件的 delete 方法 16 this.props.onDeleteItem(date); 17 } 18 19 render { 20 return ( 21 22

23 { this.props.content } 24 { this.props.date } 25 26 27 28

29 30 ) 31 } 32 } 33 34 export default TodoItem;

所以整個專案的元件之間的關係可以用下圖表示:

可以看到,父元件中定義了所有的方法,並連同獲取到得資料分發給子元件,子元件中將從父元件中獲取到的資料進行處理,同時觸發父元件中的方法,完成資料的操作。根據功能劃分元件,邏輯是十分清晰的,這也是 React 的一大優點。

最後是相關樣式檔的編寫,比較簡單,這裡貼上代碼,具體的就不分析了。

1 body { 2 padding: 50px; 3 font-size: 14px; 4 font-family: 'comic sans'; 5 color: #fff; 6 background-image: url(../images/bg2.jpg); 7 background-size: cover; 8 } 9 10 button { 11 outline: none; 12 cursor: pointer; 13 } 14 15 .container { 16 position: absolute; 17 top: 15%; 18 right: 15%; 19 width: 400px; 20 height: 475px; 21 overflow-x: hidden; 22 overflow-y: auto; 23 padding: 20px; 24 border: 1px solid #666; 25 border-radius: 5px; 26 box-shadow: 5px 5px 20px #000; 27 background: rgba(60,60,60,0.3); 28 } 29 30 .header h2 { 31 padding: 0; 32 margin: 0; 33 font-size: 25px; 34 text-align: center; 35 letter-spacing: 1px; 36 } 37 38 .todoForm { 39 margin: 20px 0 30px 0; 40 } 41 42 .todoContent { 43 display: block; 44 width: 380px; 45 padding: 10px; 46 margin-bottom: 20px; 47 border: none; 48 border-radius: 3px; 49 } 50 51 .tooltip { 52 display: inline-b lock; 53 font-size: 14px; 54 font-weight: bold; 55 color: #FF4A60; 56 } 57 58 .todoItem { 59 margin-bottom: 10px; 60 color: #333; 61 background: #fff; 62 border-radius: 3px; 63 } 64 65 .todoItem p { 66 position: relative; 67 padding: 8px 10px; 68 font-size: 12px; 69 } 70 71 .itemTime { 72 position: absolute; 73 right: 40px; 74 } 75 76 .delBtn { 77 display: none; 78 position: absolute; 79 right: 3px; 80 bottom: 2px; 81 background: #fff; 82 border: none; 83 cursor: pointer; 84 } 85 86 .todoItem p:hover .delBtn { 87 display: block; 88 } 89 90 .delBtn img { 91 height: 20px; 92 }

最後使用 webpack 進行打包,啟動項目,就可以在流覽器中看到效果了。最後附上一張控制台的圖片。

發送ajax請求實現資料庫資料的更新,再更新元件的state使之資料與後臺資料保持一致,state一更新視圖也會被重新渲染實現無刷新更新。

子組件 todo-list

1 import React from 'react'; 2 import TodoItem from './todo-item'; 3 4 class TodoList extends React.Component { 5 6 render { 7 // 獲取從父組件傳遞過來的 todolist 8 const todoList = this.props.todoList; 9 // 迴圈生成每一條 todoItem,並將 delete 方法傳遞給子元件 10 const todoItems = todoList.map((item,index) => { 11 return ( 12 18 ) 19 }); 20 21 return ( 22 23 { todoItems } 24 25 ) 26 } 27 } 28 29 export default TodoList;

子組件 todo-item

1 import React from 'react'; 2 3 class TodoItem extends React.Component { 4 5 constructor(props) { 6 super(props); 7 this.state = { 8 showDel: false // 控制刪除 icon 的顯示隱藏 9 } 10 } 11 12 handleDelete { 13 // 獲取父組件傳遞過來的 date 14 const date = this.props.date; 15 // 執行父元件的 delete 方法 16 this.props.onDeleteItem(date); 17 } 18 19 render { 20 return ( 21 22

23 { this.props.content } 24 { this.props.date } 25 26 27 28

29 30 ) 31 } 32 } 33 34 export default TodoItem;

所以整個專案的元件之間的關係可以用下圖表示:

可以看到,父元件中定義了所有的方法,並連同獲取到得資料分發給子元件,子元件中將從父元件中獲取到的資料進行處理,同時觸發父元件中的方法,完成資料的操作。根據功能劃分元件,邏輯是十分清晰的,這也是 React 的一大優點。

最後是相關樣式檔的編寫,比較簡單,這裡貼上代碼,具體的就不分析了。

1 body { 2 padding: 50px; 3 font-size: 14px; 4 font-family: 'comic sans'; 5 color: #fff; 6 background-image: url(../images/bg2.jpg); 7 background-size: cover; 8 } 9 10 button { 11 outline: none; 12 cursor: pointer; 13 } 14 15 .container { 16 position: absolute; 17 top: 15%; 18 right: 15%; 19 width: 400px; 20 height: 475px; 21 overflow-x: hidden; 22 overflow-y: auto; 23 padding: 20px; 24 border: 1px solid #666; 25 border-radius: 5px; 26 box-shadow: 5px 5px 20px #000; 27 background: rgba(60,60,60,0.3); 28 } 29 30 .header h2 { 31 padding: 0; 32 margin: 0; 33 font-size: 25px; 34 text-align: center; 35 letter-spacing: 1px; 36 } 37 38 .todoForm { 39 margin: 20px 0 30px 0; 40 } 41 42 .todoContent { 43 display: block; 44 width: 380px; 45 padding: 10px; 46 margin-bottom: 20px; 47 border: none; 48 border-radius: 3px; 49 } 50 51 .tooltip { 52 display: inline-b lock; 53 font-size: 14px; 54 font-weight: bold; 55 color: #FF4A60; 56 } 57 58 .todoItem { 59 margin-bottom: 10px; 60 color: #333; 61 background: #fff; 62 border-radius: 3px; 63 } 64 65 .todoItem p { 66 position: relative; 67 padding: 8px 10px; 68 font-size: 12px; 69 } 70 71 .itemTime { 72 position: absolute; 73 right: 40px; 74 } 75 76 .delBtn { 77 display: none; 78 position: absolute; 79 right: 3px; 80 bottom: 2px; 81 background: #fff; 82 border: none; 83 cursor: pointer; 84 } 85 86 .todoItem p:hover .delBtn { 87 display: block; 88 } 89 90 .delBtn img { 91 height: 20px; 92 }

最後使用 webpack 進行打包,啟動項目,就可以在流覽器中看到效果了。最後附上一張控制台的圖片。

同類文章
Next Article
喜欢就按个赞吧!!!
点击关闭提示