一个用react+nodejs实现的笔记本小应用
寒假回家产品经理一直叮嘱着要继续做学校团队的辣个项目,但是...,我到现在一点都还没做,而且还销声匿迹躲了起来藏了几天,是的我干了票大的,想把项目用一种新的模式给实现了,所以这几天一直在偷偷摸摸的做一些不相干的东西,不知道产品经理知道了会不会砍我...这期间为了了解前后端分离的数据交互方式写了个小笔记本应用。
这里应该有一段严肃的说明
-应用功能
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的转译.
这里应该是总结
=>没有总结