寒假回家產品經理一直叮囑着要繼續做學校團隊的辣個項目,但是...,我到現在一點都還沒做,而且還銷聲匿跡躲了起來藏了幾天,是的我干了票大的,想把項目用一種新的模式給實現了,所以這幾天一直在偷偷摸摸的做一些不相干的東西,不知道產品經理知道了會不會砍我...這期間為了了解前后端分離的數據交互方式寫了個小筆記本應用。
這里應該有一段嚴肅的說明
-應用功能
1 添加筆記
2 刪除筆記
3 顯示和隱藏表單
好吧⁄(⁄ ⁄•⁄ω⁄•⁄ ⁄)⁄.我承認這功能確實是太簡單了...
=>github完整版:react-note(已經使用redux重構)
獻上各種效果圖(因為這樣博客看起來會比較長⁄(⁄ ⁄•⁄ω⁄•⁄ ⁄)⁄):
實現思路
技術上前端用的是react框架+webpack作為構建工具,后台用的nodejs和mongodb。
總的實現思路是這樣的:利用了react當state改變會自動調用this.render方法重新渲染視圖的特點,我在整個應用組件didMounted的時候,通過ajax從數據庫拿到notes筆記記錄把它們和state綁定在一起,所以當數據改變的時候state也會改變,state一改變視圖重新被渲染,所以在添加刪除筆記的時候頁面表現的很流暢,你不需要顯式的去進行各種dom操作,因為這個過程react通過虛擬dom幫我們解決了,這也是我覺得react做的非常棒的地方。
源碼
頁面被拆分為多個子組件和一個包含這些子組件的父組件。
這是父組件
"use strict"; import React from "react"; import ReactDOM from "react-dom"; import $ from "./jquery.min.js"; import Notes_header from "./Notes_header.jsx"; import Notes_form from "./Notes_form.jsx"; import Notes_list from "./Notes_list.jsx"; class Notes extends React.Component{ constructor(props){ super(props); this.state={ notes : [], formDisplayed : false }; } componentDidMount(){ $.ajax({ url : "/init", type : "get", dataType : "json", cache : false, success : function(notes){ /*notes是從數據庫讀取到的筆記數組*/ // console.log("請求成功了!!但是...數據呢?..."); notes=this.notesSort(notes); this.setState({ notes: notes }); // console.log(this.state.notes); }.bind(this), error : function(){ console.log("視圖渲染失敗..."); } }); } onToggleForm(){ this.setState({ formDisplayed : !this.state.formDisplayed }); } onNewNote(newNote){ // console.log(JSON.stringify(newNote)); $.ajax({ url : "/addNote", type : "post", contentType : "application/json; charset=utf-8", dataType : "json", data : JSON.stringify(newNote),/*反序列化,到了服務端再被bodypaser.json()序列化*/ cache : false, success : function(notes){ console.log("筆記添加成功!"); notes=this.notesSort(notes); this.setState({ notes:notes }); }.bind(this), error : function(){ console.log("失敗..."); } }); } onDeleteNote(date){/*根據日期來刪除筆記*/ var delete_date={ date : date }; console.log(JSON.stringify(delete_date)); $.ajax({ url : "/deleteNote", type : "post", contentType : "application/json; charset=utf-8", dataType : "json", data : JSON.stringify(delete_date), cache : false, success : function(notes){ console.log("筆記已經被刪除!"); notes=this.notesSort(notes); this.setState({ notes: notes }); }.bind(this), error : function(){ console.log("失敗..."); } }); } notesSort(newNotes){ newNotes.reverse();/*將數據庫取到的notes倒序排列再顯示,即后添加上去的記錄在最前面顯示*/ return newNotes; } render(){ return( <div className="container"> <Notes_header onToggleForm={ this.onToggleForm.bind(this) }/> <div className="container_main"> <Notes_form onToggleForm={ this.onToggleForm.bind(this) } formDisplayed={ this.state.formDisplayed } onNewNote={ this.onNewNote.bind(this) }/> <Notes_list notes={ this.state.notes } onDeleteNote={ this.onDeleteNote.bind(this) }/> </div> </div> ); } } ReactDOM.render(<Notes/>,document.getElementById("app"));
在這里說一下,在react中父組件和子組件之間如何進行通信呢?父組件和子組件的通信是通過傳遞props的方式,在props中你可以傳遞父組件的state,數據,還有各種定義在父組件之中的方法,子組件也通過這種方式傳遞給子組件的子組件,這也是一直在說的單向數據流;當父組件傳遞給子組件它的方法時,子組件可以通過回調來實現和父組件的通信,即給傳給它的方法傳遞數據作為參數,父組件的方法在處理子組件傳遞的數據的過程中來實現與子組件的通信.
父組件的功能:
1 在組件DidMounted的時候通過ajax請求notes數據與state綁定實現首次渲染,
2 將數據,相應的方法分發給個子組件,
3 實現添加筆記的方法、刪除筆記的方法、和切換表單的方法,這么說吧,幾乎所有的功能都是在父組件實現的,子組件存在的意義只是在響應這些方法並給這些方法傳入一些必要的參數
添加筆記的方法被觸發的時候,發送ajax請求實現數據庫數據的更新,再更新組件的state使之數據與后台數據保持一致,state一更新視圖也會被重新渲染實現無刷新更新。
這是頭部組件
"use strict"; import React from "react"; class Notes_header extends React.Component{ render(){ return( <div className="header"> <div className="header_main"> <h2>React 筆記</h2> <input type="button" value="添加筆記" className="add_note_btn" onClick={ this.props.onToggleForm }/> </div> </div> ); } } export default Notes_header;
這個沒什么好說的,前面也說過了,響應父組件的方法,這里是響應父組件的切換表單的方法。
顯示筆記列表的組件
"use strict"; import React from "react"; import Notes_item from "./Notes_item.jsx"; class Notes_list extends React.Component{ render(){ var notes=this.props.notes; var notes_items=notes.map( (note,index) => { return <Notes_item key={ index } title={ note.title } description={ note.description } date={ note.date } onDeleteNote={ this.props.onDeleteNote }/>; }); return( <div className="notes_list"> { notes_items } </div> ); } } export default Notes_list;
這個組件還包含有一個更細的顯示每一條筆記內容的組件,組件把父組件傳給它的筆記數組,分發給它的子組件,這里是根據筆記數組的長度動態生成這些子組件的,通過數組的map方法,筆記數組有多少條記錄就生成多少個這種子組件,並各自傳一條記錄給它們。
這是這個組件的子組件
"use strict"; import React from "react"; class Notes_item extends React.Component{ handleOver(){ this.refs.delete.style.display="block"; } handleOut(){ this.refs.delete.style.display="none"; } handleDelete(){ var date=this.props.date; this.props.onDeleteNote(date); } render(){ return( <div> <div className="notes_item" onMouseOver={ this.handleOver.bind(this) } onMouseOut={ this.handleOut.bind(this) }> <h4>{ this.props.title }</h4> <p>{ this.props.description }</p> <input className="delete" ref="delete" type="button" value="刪除它" onClick={ this.handleDelete.bind(this) }/> <span className="date">{ this.props.date }</span> </div> </div> ); } } export default Notes_item;
拿到筆記記錄之后,這個小組件會把記錄的每個數據項插入到合適的標簽里。
最后說一下服務器端吧,用的是nodejs的express框架,這里是主路由模塊的各種注冊路由,就是各種api啦,react組件就是通過ajax請求這些api來得到相應的數據的,api里面通過識別請求來實現對數據庫的相應操作。
var express = require('express'); var router = express.Router(); /* GET home page. */ router.get('/', function(req, res, next) { res.render("index",{ title : "react-note" }); }); router.get('/init', function(req,res,next){/*請求參數,相應參數和負責把錯誤信息運送出來的next參數*/ var noteModel=global.dbHandle.getModel("note");/*獲取note數據庫模型,模型能直接對數據庫進行操作*/ noteModel.find({},function(err,notes){ if(err){ return next(err); }else{ res.json(notes); } }) }); router.post('/addNote', function(req,res,next){ var newNote=req.body; var noteModel=global.dbHandle.getModel("note"); noteModel.create(newNote,function(err){ if(err){ return next(err); }else{ console.log("筆記已經成功寫入數據庫啦!!!"); noteModel.find({},function(err,notes){ if(err){ console.log("咦?是怎么回事呢?"); }else{ res.json(notes); } }); } }); }); router.post('/deleteNote', function(req,res,next){ var delete_date=req.body.date; var noteModel=global.dbHandle.getModel("note"); noteModel.remove({date : delete_date},function(err){ if(err){ return next(err);/*錯誤的話,把錯誤給運出來*/ }else{ console.log("筆記已經被你殘忍的給刪除了..."); noteModel.find({},function(err,notes){ if(err){ console.log("我也不知道怎么回事..."); }else{ res.json(notes); } }); } }); }); module.exports = router;
用mongoose操作數據庫的
dbHandle.js
var mongoose=require('mongoose'); var models=require('./models.js'); var Schema=mongoose.Schema; /*根據已經規划好的數據庫模型表定義各種數據庫模型,傳入必要的模型骨架Schema和模型名(類型)*/ for( var modelName in models ){ mongoose.model( modelName , new Schema( models[ modelName ] )); } /*傳入模型名(類型)獲取到相應的模型*/ module.exports={ getModel : function( modelName ){ return _getModel( modelName ); } }; var _getModel=function( modelName ){ return mongoose.model( modelName ); }
總的來說這個數據庫操控模塊功能就是根據已經有了的數據庫模型規划表生成各種實際的 數據庫模型,並當傳入一個數據庫模型名給它時,給你返回相應的數據庫模型,得到數據庫模型你可以去操控數據庫
models.js
module.exports={ note : { title : { type : String , required : true }, description : { type : String , required : true }, date : { type : String , required : true } } };
這是webpack的配置
//webpack.config.js var path=require("path"); module.exports={ entry: "./public/javascripts/entry.js", output: { path: path.join(__dirname,"./public/out"), //打包輸出的路徑 filename: "bundle.js", //打包后的名字 publicPath: "./public/out/" }, //*大於8KB的圖片不會被打包,所以一般會被打包的都是一些css文件引入的icon圖標或者logo什么的 //在對模塊捆綁打包的過程中會對文件的后綴名進行檢測,然后進行相應的預處理 module: { loaders: [ {test: /\.js$/, loader: "babel",query: {presets: ['react','es2015']}}, /*es6 to es5*/ {test: /\.jsx$/,loader: 'babel', query: {presets: ['react', 'es2015']}}, /*jsx to js,es5 to es6*/ {test: /\.css$/, loader: "style!css"}, /*css to css*/ {test: /\.(jpg|png|otf)$/, loader: "url?limit=8192"}, /*images 打包*/ {test: /\.scss$/, loader: "style!css!sass"} /*sass to css*/ ] } };
webpack的各種加載器本身是不具備各種轉譯功能的,比如babel加載器,它自己是不能轉譯jsx文件或者es6的,但是當它檢測到jsx或者es6代碼的存在的時候,會幫你去調用babel-core還有相應的預設來轉譯它們。
我用sass寫的樣式,所以配置里用了sass加載器,記得要去下載個node-sass模塊才能支持sass的轉譯.
這里應該是總結
=>沒有總結