主要完成了基本頁面的搭建,但是對于數(shù)據(jù)的交互還并不支持,因此為了構(gòu)建一個完整的數(shù)據(jù)流,開始嘗試將應(yīng)用連接到數(shù)據(jù)庫。
數(shù)據(jù)庫選型(可以略過)
選型最基本的要求就是可用js操作。在選型階段發(fā)現(xiàn)了wilddog(野狗),以簡單的實(shí)時通信著稱。
野狗確實(shí)特別合適flux,flux是為了給react或者其他前端框架做全局?jǐn)?shù)據(jù)同步用的,野狗做的事情恰好就是各個客戶端和服務(wù)器的數(shù)據(jù)同步,試想一下,我在手機(jī)上點(diǎn)了一個贊,更新了本地?cái)?shù)據(jù),然后本地?cái)?shù)據(jù)自動同步了線上數(shù)據(jù),線上數(shù)據(jù)又自動同步了你手機(jī)上的數(shù)據(jù),然后state的改變觸發(fā)重繪,在你的界面上彈出一個小紅點(diǎn)。而這一系列的數(shù)據(jù)比對,傳遞,同步,重繪都是野狗和框架自動完成的。(摘自知乎)
而且它是也是js操作,使用事件方式來對數(shù)據(jù)庫進(jìn)行操作,而且數(shù)據(jù)庫是以后臺云方式存在的,如果只是構(gòu)建react的完整數(shù)據(jù)流,完全可以不用后臺,只搭配野狗就實(shí)現(xiàn)。我又沒用野狗,還說這么多是想表達(dá)什么呢?感慨這實(shí)在是簡單,用過的朋友希望能介紹下經(jīng)驗(yàn),后邊有機(jī)會想嘗試一把敏捷開發(fā)。
言歸正傳。
目前前端的主流方法還是利用ajax或者fetch這種方式向指定api請求數(shù)據(jù),然后router處理請求返回?cái)?shù)據(jù),如果使用野狗提供的方法,可以將這一步和對數(shù)據(jù)庫的讀寫操作綜合為一步。確實(shí)高效快捷,然而這次本著學(xué)習(xí)的目的,想一探web應(yīng)用的整個流程,還是選擇走主流,所以這次選擇使用mongodb,然后使用express作為后臺。
將webpack-dev-server集成到整個web應(yīng)用
既然選定后臺使用express作為自己的web服務(wù)器,那么第一步就需要將webpack dev server集成進(jìn)來,否則就不能使用webpack提供的模塊實(shí)時打包和熱加載功能。最簡單的方式如下:

就是在入口html文件載入打包的js文件時指定完整的url地址,如
<script src="http://127.0.0.1:3000/assets/bundle.js"></script>
告訴頁面應(yīng)該去開發(fā)服務(wù)器地址獲得腳本資源文件。使用過程中發(fā)現(xiàn),bundle.js文件是webpack會打包生成出來的,如果應(yīng)用中就只有這個一個輸出,沒有chunks的話,這樣集成是可以的。但是要知道 webpack dev server 把編譯后的靜態(tài)文件是保存在內(nèi)存里的,如果使用按需加載,就會找不到這些chunkfiles,抽取的公共文件也會找不到,所以我們不得不繼續(xù)前行。
express本身就是一系列middleware的集合,而webpack-dev-server就是一個小型的express服務(wù)器,它就是用的webpack-dev-middleware來處理webpack編譯后的輸出,所以,我們在express中使用webpack的開發(fā)工具:webpack-dev-middleware和webpack-hot-middleware。webpack-hot-middleware是結(jié)合webpack-dev-middleware使用的,用來實(shí)現(xiàn)熱更新。
使用前記得安裝。dev模式下,webpack的配置如下,只需注意注釋地方(其它地方不用關(guān)注):
'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',
//入口文件修改,原來對應(yīng)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個插件,這個原來應(yīng)該也有,如果沒有就加上
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的啟動文件中配置(為方便,貼出完整代碼,關(guān)于配置主要是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))
//設(shè)置靜態(tài)資源地址
app.use(express.static(__dirname+'/public'));
app.use('/',routes);
app.listen(3000,function(){
console.log("runing")
})
}
note:兩個middleware應(yīng)在routes之前配置。
至此,webpack-dev-server整合到了nodeJs后臺。
使用mongodb數(shù)據(jù)庫
mongodb我們都聽過,但是使用時,我們安裝和使用的是mongoose,這是一個提供了和mongodb相映射的nodeJs庫,它可以將數(shù)據(jù)庫中的數(shù)據(jù)類型轉(zhuǎn)換為js對象供我們在應(yīng)用中使用。安裝mongoose(npm install mongoose)之前要首先安裝好mongodb(brew install mongodb)。
向express啟動文件中添加
var mongoose=require('mongoose');
//導(dǎo)入定義的模型
global.dbHandle=require('./models/haddledb.js');
//連接數(shù)據(jù)庫,默認(rèn)端口號是27017,mediumReact是自己的數(shù)據(jù)庫名稱
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定義了文檔的結(jié)構(gòu),是數(shù)據(jù)庫的骨架,不具備操作數(shù)據(jù)庫的能力;Model是由Schema生成的模型,具備操作數(shù)據(jù)庫能力;Entity是Model生成的實(shí)體,其操作也能影響數(shù)據(jù)庫。一般我們都是操作model。
那么現(xiàn)在的當(dāng)務(wù)之急,就是向數(shù)據(jù)庫中添加一些數(shù)據(jù),mongoose提供的CRUD方法也很簡潔:
//可以使用model創(chuàng)建一個實(shí)體
ArticlesEntity=new ArticlesModel({
"id": 2001,
"title": "生活不是等待暴風(fēng)雨過去而是讓我們學(xué)會在雨中翩翩起舞,生活不是等待暴風(fēng)雨過去而是讓我們學(xué)會在雨中翩翩起舞。",
"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"
});
//然后保存到數(shù)據(jù)庫
ArticlesEntity.save();
但是,對于我們初來乍到的人來說,最好的還是可視化工具,我使用的是Robomongo,稍微看兩下其菜單,就能知道如何操作了。
建立路由api,完善請求流程
新聞列表頁請求數(shù)據(jù)的antion:
import fetch from 'isomorphic-fetch'
import { createActions } from 'redux-actions';
export const { fetchArticles } = createActions({
FETCH_ARTICLES: async () => {
try {
//express后臺中需要建立'/articles'路由,來處理請求數(shù)據(jù)
let response = await fetch('/articles');
let articles = await response.json();
return { articles }
} catch (err) {
console.log(err);
}
}
});
對應(yīng)的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,因?yàn)槭褂昧藃eact-router來處理處理做單頁應(yīng)用,在express中我們就只用給其一個入口路徑
router.get('/',function(req,res,next){
res.render('index',{title:"medium-react"});
});
//列表頁get數(shù)據(jù)的請求地址
router.get('/articles',function(req,res,next){
//使用find()方法有點(diǎn)簡單了,因?yàn)槭醉摶緯婕胺猪摚@個以后再改進(jìn)
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應(yīng)用就已經(jīng)搭建完成,這一章讓我體驗(yàn)了一把完整的web應(yīng)用的基本流程,感覺不錯。但是遺留問題也還很多,比如連接數(shù)據(jù)庫的初衷,就是更好的觀察、處理異步交互,然而在文章詳情頁,整個結(jié)構(gòu)樹太深

目前只在文章詳情頁connect到數(shù)據(jù)庫,也就意味著dispatch方法是在這個容器中獲取的,但是對于評論中的點(diǎn)贊事件可能發(fā)生在主回復(fù)Comment組件中,也可能是從回復(fù)Reply組件中,如果將dispatch 方法一層層傳遞下去,不僅路徑深,而且中間都不曾用到該方法?;蛟S也可以這樣:點(diǎn)贊之后將數(shù)據(jù)傳遞(比如觀察者模式)到container ,然后dispatch這個請求。你還有什么好的方法么?
下一節(jié)先實(shí)現(xiàn)最基礎(chǔ)的異步交互:
【主要參考資源】
Express結(jié)合Webpack的全棧自動刷新
express使用指南
Mongoose學(xué)習(xí)參考文檔