以前用 Ant Design 開發了一個項目,所以對 React 的特性有了必定的瞭解,React 使用封裝組件的思想,組件各自維護本身的狀態和 UI, 組件之間經過 props 傳遞數據和方法。當狀態更新時自動重繪整個組件,從而達到局部刷新的效果,大大提升了 DOM 更新的效率,同時組件化十分有利於維護。在對 React 進行進一步的學習後,使用 Node.js + React 的方式實現了一個簡單的 TodoList 單頁應用,同時涉及簡單的 MongoDB 數據庫操做,總的來講,項目相對簡單,十分適合 React 的入門學習。css
Github地址: https://github.com/wx1993/Node-React-MongoDB-TodoListhtml
應用功能node
一、添加 todoListreact
二、刪除 todoListjquery
應用效果圖webpack
項目運行環境:git
Windows/Maces6
Node.js v6.9.4 or latergithub
MongoDBweb
安裝和配置 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)
接下來看一下項目的目錄結構:
首先看入口頁面 index.ejs
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <title><%= title %></title> 5 <link rel='stylesheet' href='/css/style.css' /> 6 </head> 7 <body> 8 9 <div id="app"> 10 11 </div> 12 13 <script src="/js/bundle.js"></script> 14 </body> 15 </html>
入口文件 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 <Todo />, 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 <div className="container"> 120 <h2 className="header">Todo List</h2> 121 <form className="todoForm" ref="todoForm" onSubmit={ this.handleSubmit.bind(this) }> 122 <input ref="content" type="text" placeholder="Type content here..." className="todoContent" /> 123 { this.state.showTooltip && 124 <span className="tooltip">Content is required !</span> 125 } 126 </form> 127 <TodoList todoList={this.state.todoList} onDeleteItem={this._onDeleteItem.bind(this)} /> 128 </div> 129 ) 130 } 131 } 132 133 export default Todo;
父組件的功能:
一、在組件 DidMounted 時經過 ajax 請求全部的數據與 state 綁定實現首次渲染;
二、將數據,相應的方法分發給個子組件;
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 <TodoItem 13 key={index} 14 content={item.content} 15 date={item.date} 16 onDeleteItem={this.props.onDeleteItem} 17 /> 18 ) 19 }); 20 21 return ( 22 <div> 23 { todoItems } 24 </div> 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 <div className="todoItem"> 22 <p> 23 <span className="itemCont">{ this.props.content }</span> 24 <span className="itemTime">{ this.props.date }</span> 25 <button className="delBtn" onClick={this.handleDelete.bind(this)}> 26 <img className="delIcon" src="/images/delete.png" /> 27 </button> 28 </p> 29 </div> 30 ) 31 } 32 } 33 34 export default TodoItem;
因此整個項目的組件之間的關係能夠用下圖表示:
能夠看到,父組件中定義了全部的方法,並連同獲取到得數據分發給子組件,子組件中將從父組件中獲取到的數據進行處理,同時觸發父組件中的方法,完成數據的操做。根據功能劃分組件,邏輯是十分清晰的,這也是 React 的一大優勢。
最後是相關樣式文件的編寫,比較簡單,這裏貼上代碼,具體的就不分析了。
style.css
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 進行打包,啓動項目,就能夠在瀏覽器中看到效果了。最後附上一張控制檯的圖片。