上一篇文章:
react+redux实战(一)----基本流程
主要完成了基本页面的搭建,但是对于数据的交互还并不支持,因此为了构建一个完整的数据流,开始尝试将应用连接到数据库。
数据库选型(可以略过)
选型最基本的要求就是可用js操作。在选型阶段发现了wilddog(野狗),以简单的实时通信著称。
野狗确实特别合适flux,flux是为了给react或者其他前端框架做全局数据同步用的,野狗做的事情恰好就是各个客户端和服务器的数据同步,试想一下,我在手机上点了一个赞,更新了本地数据,然后本地数据自动同步了线上数据,线上数据又自动同步了你手机上的数据,然后state的改变触发重绘,在你的界面上弹出一个小红点。而这一系列的数据比对,传递,同步,重绘都是野狗和框架自动完成的。(摘自知乎)
而且它是也是js操作,使用事件方式来对数据库进行操作,而且数据库是以后台云方式存在的,如果只是构建react的完整数据流,完全可以不用后台,只搭配野狗就实现。我又没用野狗,还说这么多是想表达什么呢?感慨这实在是简单,用过的朋友希望能介绍下经验,后边有机会想尝试一把敏捷开发。
言归正传。
目前前端的主流方法还是利用ajax或者fetch这种方式向指定api请求数据,然后router处理请求返回数据,如果使用野狗提供的方法,可以将这一步和对数据库的读写操作综合为一步。确实高效快捷,然而这次本着学习的目的,想一探web应用的整个流程,还是选择走主流,所以这次选择使用mongodb,然后使用express作为后台。
将webpack-dev-server集成到整个web应用
既然选定后台使用express作为自己的web服务器,那么第一步就需要将webpack dev server集成进来,否则就不能使用webpack提供的模块实时打包和热加载功能。最简单的方式如下:
就是在入口html文件载入打包的js文件时指定完整的url地址,如
<script src="http://127.0.0.1:3000/assets/bundle.js"></script>
告诉页面应该去开发服务器地址获得脚本资源文件。使用过程中发现,bundle.js文件是webpack会打包生成出来的,如果应用中就只有这个一个输出,没有chunks的话,这样集成是可以的。但是要知道 webpack dev server 把编译后的静态文件是保存在内存里的,如果使用按需加载,就会找不到这些chunkfiles,抽取的公共文件也会找不到,所以我们不得不继续前行。
express本身就是一系列middleware的集合,而webpack-dev-server就是一个小型的express服务器,它就是用的webpack-dev-middleware来处理webpack编译后的输出,所以,我们在express中使用webpack的开发工具:webpack-dev-middleware和webpack-hot-middleware。webpack-hot-middleware是结合webpack-dev-middleware使用的,用来实现热更新。
使用前记得安装。dev模式下,webpack的配置如下,只需注意注释地方(其它地方不用关注):
'use strict';
let path = require('path');
let webpack = require('webpack');
let baseConfig = require('./base');
let defaultSettings = require('./defaults');
// Add needed plugins here,reload为true意思是,如果遇到不能hot load 的情况,就整页刷新
var hotMiddlewareScript = 'webpack-hot-middleware/client?reload=true';
let config = Object.assign({}, baseConfig, {
entry: [
'./src/index',
//入口文件修改,原来对应webpack-dev-server的是
//'webpack-dev-server/client?http://0.0.0.0:8000',
//'webpack/hot/only-dev-server',改为如下:
hotMiddlewareScript
],
cache: true,
devtool: 'eval-source-map',
plugins: [
//添加下面3个插件,这个原来应该也有,如果没有就加上
new webpack.optimize.OccurenceOrderPlugin(),
new webpack.HotModuleReplacementPlugin(),
new webpack.NoErrorsPlugin()
],
module: defaultSettings.getDefaultModules()
});
config.module.loaders.push({
test: /\\.(js|jsx)$/,
loader: 'react-hot!babel-loader',
include: [].concat(
config.additionalPaths,
[ path.join(__dirname, '/../src') ]
)
});
module.exports = config;
note:就只有注释的几个地方需要注意修改,webpack原来如何配置的,其它地方维持不变就行。
接下来在express的启动文件中配置(为方便,贴出完整代码,关于配置主要是if判断中的语句):
var express=require('express');
var routes=require('./routes/index');
var app=express();
//nodeJs模板语言,选用ejs(需要安装),如下配置可正常使用.html文件作为入口
app.engine('.html', require('ejs').__express);
//change the template main catelog
app.set('views',__dirname+'/src');
app.set('view engine','html')
var isDev = process.env.NODE_ENV !== 'production';
if(isDev){
const webpack = require('webpack');
const webpackDevMiddleware = require('webpack-dev-middleware');
const webpackHotMiddleware = require('webpack-hot-middleware');
const config = require('./webpack.config');
const compiler=webpack(config);
app.use(webpackDevMiddleware(compiler,{
publicPath:config.output.publicPath,
noInfo:true,
stats:{
colors:true
}
}));
app.use(webpackHotMiddleware(compiler))
//设置静态资源地址
app.use(express.static(__dirname+'/public'));
app.use('/',routes);
app.listen(3000,function(){
console.log("runing")
})
}
note:两个middleware应在routes之前配置。
至此,webpack-dev-server整合到了nodeJs后台。
使用mongodb数据库
mongodb我们都听过,但是使用时,我们安装和使用的是mongoose,这是一个提供了和mongodb相映射的nodeJs库,它可以将数据库中的数据类型转换为js对象供我们在应用中使用。安装mongoose(npm install mongoose)之前要首先安装好mongodb(brew install mongodb)。
向express启动文件中添加
var mongoose=require('mongoose');
//导入定义的模型
global.dbHandle=require('./models/haddledb.js');
//连接数据库,默认端口号是27017,mediumReact是自己的数据库名称
global.db=mongoose.connect('mongodb://localhost:27017/mediumReact');
models/haddledb.js文件中是定义好的模型
var mongoose=require('mongoose');
var Schema=mongoose.Schema;
//定义一个Schema
var ArticlesSchema=new Schema({
id:{type:Number},
title:{type:String},
genre:{type:Number},
source:{type:String},
praise_count:{type:Number},
comment_count:{type:Number},
publish_time:{type:Date},
banner_pic:{type:String}
});
//定义一个model
var ArticlesModel=mongoose.model("Articles",ArticlesSchema);
主要就是理解Schema定义了文档的结构,是数据库的骨架,不具备操作数据库的能力;Model是由Schema生成的模型,具备操作数据库能力;Entity是Model生成的实体,其操作也能影响数据库。一般我们都是操作model。
那么现在的当务之急,就是向数据库中添加一些数据,mongoose提供的CRUD方法也很简洁:
//可以使用model创建一个实体
ArticlesEntity=new ArticlesModel({
"id": 2001,
"title": "生活不是等待暴风雨过去而是让我们学会在雨中翩翩起舞,生活不是等待暴风雨过去而是让我们学会在雨中翩翩起舞。",
"genre": 1,
"source": "片刻",
"praise_count": 234,
"comment_count": 65,
"publish_time": "2016-08-10 14:08:36 +0800",
"banner_pic": "/images/grid-article-banner.jpg"
});
//然后保存到数据库
ArticlesEntity.save();
但是,对于我们初来乍到的人来说,最好的还是可视化工具,我使用的是Robomongo,稍微看两下其菜单,就能知道如何操作了。
建立路由api,完善请求流程
新闻列表页请求数据的antion:
import fetch from 'isomorphic-fetch'
import { createActions } from 'redux-actions';
export const { fetchArticles } = createActions({
FETCH_ARTICLES: async () => {
try {
//express后台中需要建立'/articles'路由,来处理请求数据
let response = await fetch('/articles');
let articles = await response.json();
return { articles }
} catch (err) {
console.log(err);
}
}
});
对应的reducer:
import { handleActions } from 'redux-actions';
import { FETCH_ARTICLES } from '../actions/index.js';
export default handleActions({
FETCH_ARTICLES: (state, action) => {
let payload = action.payload;
return {...state,isFecting:false,articles:payload.articles}
}
}, {});
express的routes文件:
var express=require('express');
var router=express.Router();
var mongoose=require('mongoose');
var articles=mongoose.model('Articles');
//get home page,因为使用了react-router来处理处理做单页应用,在express中我们就只用给其一个入口路径
router.get('/',function(req,res,next){
res.render('index',{title:"medium-react"});
});
//列表页get数据的请求地址
router.get('/articles',function(req,res,next){
//使用find()方法有点简单了,因为首页基本会涉及分页,这个以后再改进
articles.find({},function(err,results){
if(err){
console.log('error message',err);
return;
}
res.json(results);
})
});
module.exports=router;
这个routes文件是在express的启动文件中使用的:
var routes=require('./routes/index');
app.use('/',routes);
写在最后
至此,整个web应用就已经搭建完成,这一章让我体验了一把完整的web应用的基本流程,感觉不错。但是遗留问题也还很多,比如连接数据库的初衷,就是更好的观察、处理异步交互,然而在文章详情页,整个结构树太深
目前只在文章详情页connect到数据库,也就意味着dispatch方法是在这个容器中获取的,但是对于评论中的点赞事件可能发生在主回复Comment组件中,也可能是从回复Reply组件中,如果将dispatch 方法一层层传递下去,不仅路径深,而且中间都不曾用到该方法。或许也可以这样:点赞之后将数据传递(比如观察者模式)到container ,然后dispatch这个请求。你还有什么好的方法么?
下一节先实现最基础的异步交互: