基于React服务器端渲染的博客系统
目录
1. 开发前准备
1.1 技术选型
1.2 整体设计
1.3 构建开发
2. 技术点
2.1 react
2.2 redux, react-router
2.3 server-render
3. 总结
正文
1. 开发前准备
1. 1 技术选型
对于个人的博客系统而言,服务器计算能力往往不是需要考虑,而其中的 I/O 操作是比较复杂的,同理对前端的交互要求也是较高的,所以这次主要还是围绕 Node系
,React系
框架进行开发。对于 2016 年后的互联网产品, React
搭建的 SPA ( single page ) 以及 React-Native
搭建的类似 hybrid 的原生应用,这样的开发模式,会引导今后 3 年产品的开发。
1.2 整体设计
图描绘的很简单,简单来说前端采用 React + React-router + Redux 以 flux 模式进行开发,后端采用了两个 Node Server 提供 React 服务器端渲染和数据提供,这里可以先看为一个服务器,为啥要采用两个,后面会提到。如果你有开发前后端分离项目的经验,你可以将提供 React 服务器端渲染这一块与前端合并,我们可以在上面书写 xtpl , ejs , jade 这些模板,开发起来更为流畅。
由于我的服务器是在华北地区,加上 React 这一系列的库又都是非常大的,即使我将 js , css 文件都打包成一个文件,访问起资源加载也是特别慢,所以 CDN 也是必不可少的。
1.3 构建开发
对工程完成大致设计后,我们需要利用前端工具做一些利于开发的构建,可以大致分为以下 3 个任务。
method1: 本地服务器的热加载
开发静态组件阶段为了提升开发效率,减少各位小白对 F5 的使用量,我们一般使用 webpack-dev-server
做本地服务,与一般的 webpack 配置文件不同之处也就在于入口点 entry 多加了点东西。
entry: {
app: [
'webpack-dev-server/client?http://localhost:3000',
'webpack/hot/only-dev-server',
path.resolve(__dirname, '../views/index.js')
]
}
将 webpack 中的选项传入到 webpack-dev-server (一个 express 服务器) 对象
new WebpackDevServer(webpack(webpackConfig), {
publicPath: '/',
hot: true, // 热替换开启
historyApiFallback: true, // 无刷更改url,配合react-router使用
inline: true, // 热替换开启
stats: {
colors: true
}
}).listen(PORT, FUNC);
接着启动你的服务器即可完成构建。
method2: 静态资源分离
通常在开发阶段会有 import ./xxx.less
或 require('xxx.png')
ES5 / ES6 语法来引入静态资源, 在打包时我们需要把所有静态文件和我们的组件解耦在合并成一个文件,这同样需要 webpack 来完成,同时要引入名为 extract-text-webpack-plugin 的插件,具体使用如下
var ExtractTextPlugin = require('extract-text-webpack-plugin');
module.export = {
module: {
loaders: [
... // other settings
{
test: /\.css/,
loader: ExtractTextPlugin.extract('style', ['css'])
},
{
test: /\.less/,
loader: ExtractTextPlugin.extract('style', ['css', 'less'])
},
]
},
plugins: [
new ExtractTextPlugin('[name].css'),
... // other settings
]
}
method3: 服务器端渲染
首先,明确一点的是 node 稳定的 4.x 版本并不能支持所有 ES6 的语法,因此采用 ES6 的语法写 node 服务器端,需要先转到 ES5 再执行,一般来说有两种方法。一是下载 babel-node
到操作系统直接执行,二是利用 webpack 引入 babel-loader
,babel-core
打包(其实两种方法是一种思想)。所以按照正常 ES6 -> ES5 的构建流程打包即可。
module.export = {
node: {
fs: 'empty',
net: 'empty',
// 防止打包错乱
__filename: true,
__dirname: true
}
}
如果打包过程中出现了大量有关 fs , net 模块的报错或者打包后出现了文件引入的错误,添加上述 webpack 选项即可解决问题,这两种都是在 node 端打包经常出现的问题。
2. 开发
先来看看整个工程目录是怎样的。
build: 上述中各构建文件
server: 用于服务器端渲染的文件
views: 前端组件及redux状态管理文件系统
2.1 react
对于 react 语法这里就不提了,重点提下文件的组织方式,以便我们开发和日常维护。若只针对于工作量不大 react ,目录很简单,只需区分展示级组件
( compnent ) 和 复合型组件
( containers )。
--- views/
--- components/ 各展示级组件
--- containers/各复合型组件
具体呈现形式可在 github 上了解
2.2 redux, react-router
刚开始写 react 的时候总是觉得 redux 和 react-router 是一个哗众取宠的存在,直到数据量和逻辑变复杂,后台逻辑愈加复杂,而前端的 react 组件还是如同开始的上手练习一样,一直做着自顶向下的数据传递和渲染,每次更新数据我们都会请求服务器,这无疑增加了服务器的压力(对较大型应用),同时没有发挥react的长处,所谓的单页应用更是纸上谈兵。
那将三者结合会是怎样的开发体验和用户体验呢?
redux
上图是 facebook 提出 flux
架构思想,是专门用于软件的结构问题的,对于 react 开发它提供一种非常简单清晰的思想。
View
: 视图层 ( react 组件)Action (动作)
: 视图层发出的消息(比如 mouseClick )Dispatcher (派发器)
: 用来接收 Actions 、执行回调函数Store (数据层)
: 用来存放应用的状态,一旦发生变动,就提醒 View 要更新页面
而 redux 提供了 action , dispatcher( redux 中是 reducer ), store 的实现 ,如何实现的呢?就拿个加载某一页数据做个例子。
action.js : loadThisPage 是一个action creater,每次调用就能创建一个带有 LOAD_THIS_PAGE 指令的对象给 reducer。
const LOAD_THIS_PAGE = 'LOAD_THIS_PAGE';
const loadThisPage = (pN) => {
return {
type: LOAD_THIS_PAGE,
pN
}
}
reducer.js : 在 reducer 中,通过判断指令来调用 store 中的数据改变状态
const LOAD_THIS_PAGE = 'LOAD_THIS_PAGE';
const articleReducer = (state = {}, action) => {
switch(action.type) {
case LOAD_THIS_PAGE:
return {
allArticles: state.allArticles,
articles: state.allArticles.slice(0, action.pN * 5)
};
default:
return state;
}
};
store.js : 创建一个 store 对象,可以通过调用这个对象的 dispatch
方法传入 action。
import { createStore, applyMiddleware} from 'redux';
const store = createStore(
reducers,
applyMiddleware(thunk) // 若需要redux中间件才添加
);
这些工作做完后,还需要将 store 和组件进行一个关联,也就是说组件要能获得 redux store。
import { Provider, connect } from 'react-redux';
// 通过connect连接组件和store
const HomeApp = connect(
(state) => {
return {
articles: state.articleReducer.articles,
articleLen: state.articleReducer.allArticles.length
};
}
)(Home);
const routes = (
<Route path="/" component={ LeftArea }>
<IndexRoute component={ HomeApp } />
</Route>
);
// store通过Provider传入到react组件
render(
<Provider store={store} key="index">
<Router history={browserHistory} routes={routes}>
</Router>
</Provider>, document.getElementById('app')
);
react-router
相比 redux , react-router 理解起来就好多了,每当 url 发生变化,改变的只是组件,而不会重新刷新页面。
对于这样一个SPA,用户访问只需加载一次数据,之后的逻辑都由浏览器来完成了~所以对交互性来说也是很大的提升。
2.3 server render
为什么需要去实现 server render ? 也就是说服务器端渲染比客户端渲染优在哪里?
server render vs client render
服务器端渲染更利于 SEO ,更容易被爬虫识别
服务器端渲染可以增加访问速度,用户访问时直接推送已经渲染好的 react 页面,不同于客户端渲染的将 react 放入浏览器中渲染。同时减少用户CPU开销,获得更好交互性,解决了首屏加载慢的问题。
分离前后端逻辑,利于开发
如何做到 react 服务器端渲染,其实 facebook 很早就给出了解决方案,并推出了 react-dom/server 库。
import { renderToString } from 'react-dom/server';
const html = renderToString(
<Provider store={store}>
<RoutingContext {...renderProps} />
</Provider>
);
最初的一个问题
为什么会采用两个node服务器呢?因为在进行 webpack 对 Node 的打包时,会将数据库的相关信息打包到一起,放在网络上是不安全的,所以需要创建另一个服务器专门用提供持久化数据服务。
3. 总结
这篇博文没有对技术做太多深入的探究,但提供了一个比较清晰地开发思路,刚从事开发工作或者还是小白的你可能在独立开发一个工程时会存在很多疑问,从构建到设计,从性能到安全都是需要考虑的。
今天微信小程序也正式上线了,赶紧去尝尝鲜~