React Redux Sever Rendering实战
# React Redux Sever Rendering(Isomorphic JavaScript)

## 前言
由于可能有些读者没听过 [Isomorphic JavaScript](http://isomorphic.net/) 。因此在进到开发 React Redux Sever Rendering 应用程式的主题之前我们先来聊聊 Isomorphic JavaScript 这个议题。
根据 [Isomorphic JavaScript](http://isomorphic.net/) 这个网站的说明:
>Isomorphic JavaScript
Isomorphic JavaScript apps are JavaScript applications that can run both client-side and server-side.
The backend and frontend share the same code.
Isomorphic JavaScript 系指浏览器端和伺服器端共用 JavaScript 的程式码。
另外,除了 Isomorphic JavaScript 外,读者或许也有听过 Universal JavaScript 这个用词。那什麽是 Universal JavaScript 呢?它和 Isomorphic JavaScript 是指一样的意思吗?针对这个议题网路上有些开发者提出了自己的观点: [Universal JavaScript](https://medium.com/@mjackson/universal-javascript-4761051b7ae9#.67xsay73m)、[Isomorphism vs Universal JavaScript](https://medium.com/@ghengeveld/isomorphism-vs-universal-javascript-4b47fb481beb#.qvggcp3v8)。其中 Isomorphism vs Universal JavaScript 这篇文章的作者 Gert Hengeveld 指出 `Isomorphic JavaScript` 主要是指前后端共用 JavaScript 的开发方式,而 `Universal JavaScript` 是指 JavaScript 程式码可以在不同环境下运行,这当然包含浏览器端和伺服器端,甚至其他环境。也就是说 `Universal JavaScript` 在意义上可以涵盖的比 `Isomorphic JavaScript` 更广泛一些,然而在 Github 或是许多技术讨论上通常会把两者视为同一件事情,这部份也请读者留意。
## Isomorphic JavaScript 的好处
在开始真正撰写 Isomorphic JavaScript 前我们在进一步探讨使用 Isomorphic JavaScript 有哪些好处?在谈好处之前,我们先看看最早 Web 开发是如何处理页面渲染和 state 管理,还有遇到哪些挑战。
最早的时候我们谈论 Web 很单纯,都是由 Server 端进行模版的处理,你可以想成 template 是一个函数,我们传送资料进去,template 最后产生一张 HTML 给浏览器显示。例如:Node 使用的([EJS](http://ejs.co/)、[Jade](http://jade-lang.com/))、Python/Django 的 [Template](https://docs.djangoproject.com/el/1.10/ref/templates/) 或替代方案 [Jinja](https://github.com/pallets/jinja)、PHP 的 [Smarty](http://www.smarty.net/)、[Laravel](https://laravel.com/) 使用的 [Blade](https://laravel.com/docs/5.0/templates),甚至是 Ruby on Rails 用的 [ERB](http://guides.rubyonrails.org/layouts_and_rendering.html)。都是由后端去 render 所有资料和页面,前端处理相对单纯。
然而随著前端工程的软体工程化和使用者体验的要求,开始出现各式前端框架的百花齐放,例如:[Backbone.js](http://backbonejs.org/)、[Ember.js](http://emberjs.com/) 和 [Angular.js](https://angularjs.org/) 等前端 MVC (Model-View-Controller) 或 MVVM (Model-View-ViewModel) 框架,将页面于前端渲染的不刷页单页式应用程式(Single Page App)也因此开始流行。
后端除了提供初始的 HTML 外,还提供 API Server 让前端框架可以取得资料用于前端 template。複杂的逻辑由 ViewModel/Presenter 来处理,前端 template 只处理简单的是否显示或是元素迭代的状况,如下图所示:

然而前端渲染 template 虽然有它的好处但也遇到一些问题包括效能、SEO 等议题。此时我们就开始思考 Isomorphic JavaScript 的可能性:为什麽我们不能前后端都使用 JavaScript 甚至是 React?

事实上,React 的优势就在于它可以很优雅地实现 Server Side Rendering 达到 Isomorphic JavaScript 的效果。在 `react-dom/server` 中有两个方法 `renderToString` 和 `renderToStaticMarkup` 可以在 server 端渲染你的 components。其主要都是将 React Component 在 Server 端转成 DOM String,也可以将 props 往下传,然而事件处理会失效,要到 client-side 的 React 接收到后才会把它加上去(但要注意 server-side 和 client-side 的 checksum 要一致不然会出现错误),这样一来可以提高渲染速度和 SEO 效果。`renderToString` 和 `renderToStaticMarkup` 最大的差异在于 `renderToStaticMarkup` 会少加一些 React 内部使用的 DOM 属性,例如:`data-react-id`,因此可以节省一些资源。
使用 `renderToString` 进行 Server 端渲染:
```javascript
import ReactDOMServer from 'react-dom/server';
ReactDOMServer.renderToString(<HelloButton name="Mark" />);
```
渲染出来的效果:
```html
<button data-reactid=".7" data-react-checksum="762752829">
Hello, Mark
</button>
```
总的来说使用 Isomorphic JavaScript 会有以下的好处:
1. 有助于 SEO
2. Rendering 速度较快,效能较佳
3. 放弃蹩脚的 Template 语法拥抱 Component 元件化思考,便于维护
4. 尽量前后端共用程式码节省开发时间
不过要注意的是如果有使用 Redux 在 Server Side Rendering 中,其流程相对複杂,不过大致流程如下:
由后端预先载入需要的 initialState,由于 Server 渲染必须全部都转成 string,所以先将 state 先 dehydration(脱水),等到 client 端再 rehydration(覆水),重建 store 往下传到前端的 React Component。
而要把资料从伺服器端传递到客户端,我们需要:
1. 把取得初始 state 当做参数并对每个请求建立一个全新的 Redux store 实体
2. 选择性地 dispatch 一些 action
3. 把 state 从 store 取出来
4. 把 state 一起传到客户端
接下来我们就开始动手实作一个简单的 React Server Side Rendering app
## 专案成果截图

## Server Rendering
获取数据可以调用 action,routes 在服务器端的处理参考 react-router server rendering,在服务器端用一个 match 方法将拿到的 request url 匹配到我们之前定义的 routes,解析成和客户端一致的 props 对象传递给组件。
./devServer.js
```
var express = require('express');
var webpack = require('webpack');
var config = require('./webpack.config.dev');
import React from 'react';
import { renderToString } from 'react-dom/server';
import { RouterContext, match } from 'react-router';
import { Provider } from 'react-redux';
import createRouter from './client/routes';
import configureStore from './client/store';
var app = express();
var compiler = webpack(config);
import comments from './client/data/comments';
import posts from './client/data/posts';
// create an object for the default data
const defaultState = {
posts,
comments
};
app.use(require('webpack-dev-middleware')(compiler, {
noInfo: true,
publicPath: config.output.publicPath
}));
app.use(require('webpack-hot-middleware')(compiler));
function renderFullPage(html, initialState) {
return `
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<title>isomorphic-redux-app</title>
<link rel="shortcut icon" type="image/png" href="http://obl1r1s1x.bkt.clouddn.com/bitbug_favicon.ico"/>
</head>
<body>
<div id="root">${html}</div>
<script>
window.__INITIAL_STATE__ = ${JSON.stringify(initialState)};
</script>
<script src="/static/bundle.js"></script>
</body>
</html>
`;
}
app.use((req, res) => {
const store = configureStore(defaultState);
const routes = createRouter();
const state = store.getState();
match({ routes, location: req.url }, (err, redirectLocation, renderProps) => {
if (err) {
res.status(500).end(`Internal Server Error ${err}`);
} else if (redirectLocation) {
res.redirect(redirectLocation.pathname + redirectLocation.search);
} else if (renderProps) {
const html = renderToString(
<Provider store={store}>
<RouterContext {...renderProps} />
</Provider>
);
res.end(renderFullPage(html, store.getState()));
} else {
res.status(404).end('Not found');
}
});
});
app.listen(7770, 'localhost', function(err) {
if (err) {
console.log(err);
return;
}
console.log('Listening at http://localhost:7770');
});
```
服务器端渲染部分可以直接通过共用客户端 store.dispatch(action) 来统一获取 Store 数据。另外注意 renderFullPage 生成的页面 HTML 在 React 组件 mount 的部分(<div id="root">),前后端的 HTML 结构应该是一致的。然后要把 store 的状态树写入一个全局变量(__INITIAL_STATE__),这样客户端初始化 render 的时候能够校验服务器生成的 HTML 结构,并且同步到初始化状态,然后整个页面被客户端接管。
本项目地址:[React-Redux-Server-Rendering](https://github.com/cllgeek/React-Redux-Server-Rendering)
React Redux Sever Rendering实战的更多相关文章
- React前端有钱途吗?《React+Redux前端开发实战》学起来
再不学React就真的跟不上大前端的形式了,目前几乎所有前端的招聘条件都是精通React者优先,看看拉勾网的React薪资,都是15K-20K,这个暑假,必须动起来了. 如果你熟悉JavaScript ...
- 《React+Redux前端开发实战》笔记3:基于Webpack构建的Hello World案例(下)
2.使用React编码 下面正式开始使用React来编写前端代码. (1)npm安装react和react-dom: npm install react react-dom -S (2)用下面代码替换 ...
- 《React+Redux前端开发实战》笔记2:基于Webpack构建的Hello World案例(上)
这次搭建分为两部分:一部分是前期必要配置,一部分是开发React代码. [基于Webpack的React Hello World项目] 1.前期必要配置 (1)首先要确保读者的开发设备上已经安装过No ...
- 《React+Redux前端开发实战》笔记1:不涉及React项目构建的Hello World案例
本小节实现一个不涉及项目构建的Hello World. [React的第一个Hello World网页] 源码地址:https://jsfiddle.net/allan91/2h1sf0ky/8/ & ...
- 【原】react+redux实战
摘要:因为最近搞懂了redux的异步操作,所以觉得可以用react+redux来做一个小小的项目了,以此来加深一下印象.切记,是小小的项目,所以项目肯定是比较简单的啦,哈哈. 项目效果图如图所示:(因 ...
- React+Redux开发实战项目【美团App】,没你想的那么难
README.md 前言 开始学习React的时候,在网上找了一些文章,读了官网的一些文档,后来觉得React上手还是蛮简单的, 然后就在网上找了一个React实战的练手项目,个人学完之后觉得这个项目 ...
- React.js 入门与实战之开发适配PC端及移动端新闻头条平台课程上线了
原文发表于我的技术博客 我在慕课网的「React.js 入门与实战之开发适配PC端及移动端新闻头条平台」课程已经上线了,文章中是目前整个课程的大纲,以后此课程还会保持持续更新,此大纲文档也会保持更新, ...
- webpack+react+redux+es6开发模式
一.预备知识 node, npm, react, redux, es6, webpack 二.学习资源 ECMAScript 6入门 React和Redux的连接react-redux Redux 入 ...
- react+redux教程(六)redux服务端渲染流程
今天,我们要讲解的是react+redux服务端渲染.个人认为,react击败angular的真正“杀手锏”就是服务端渲染.我们为什么要实现服务端渲染,主要是为了SEO. 例子 例子仍然是官方的计数器 ...
随机推荐
- Java每日一则-002
Java中包的层级关系 java中的包在逻辑上是没有套嵌的,也就是说: java.lang 和 java.lang.awt 是两个平行的包,地位相等,互不相关.只不过一个名字叫java.lang另一个 ...
- C#调用dll(C++(Win32))时的类型转换总结(转)
http://www.cnblogs.com/lidabo/archive/2012/06/05/2536737.html C++(Win 32) C# char** 作为输入参数转为char ...
- CDH版HDFS Block Balancer方法
命令: sudo -u hdfs hdfs balancer 默认会检查每个datanode的磁盘使用情况,对磁盘使用超过整个集群10%的datanode移动block到其他datanode达到均衡作 ...
- JSON解析关联类型发生死循环 There is a cycle in the hierarchy!
解决办法是忽略掉关联类型的数据,使用jsonConfig进行配置,代码如下: JsonConfig jsonConfig = new JsonConfig(); //建立配置文件 jsonConfi ...
- hello, angular
开始系统学习一下angular!首先是hello world.根据官网给出的例子,我们一下做出下面这个东西: <!DOCTYPE html> <html ng-app> < ...
- DataSnap与FireDAC三层
相交资料: http://blog.csdn.net/shuaihj/article/details/6129131http://www.cnblogs.com/hnxxcxg/p/4007876.h ...
- HTTP常见错误 400/401/403/404/500及更多
HTTP 错误 400 400 请求出错 由于语法格式有误,服务器无法理解此请求.不作修改,客户程序就无法重复此请求. HTTP 错误 401 401.1 未授权:登录失败 此错误表明传输给服务器的证 ...
- Codeforces Round #350 (Div. 2) E. Correct Bracket Sequence Editor (链表)
题目链接:http://codeforces.com/contest/670/problem/E 给你n长度的括号字符,m个操作,光标初始位置是p,'D'操作表示删除当前光标所在的字符对应的括号字符以 ...
- C# App.config文件的使用
App.config文件 1. 配置文件概述: 应用程序配置文件是标准的 XML 文件,XML 标记和属性是区分大小写的.它是可以按需要更改的,开发人员可以使用配置文件来更改设置,而不必重编译应用程序 ...
- HttpWebRequest和HttpWebResponse用法小结
http://www.cnblogs.com/willpan/archive/2011/09/26/2176475.html http://www.cnblogs.com/lip0121/p/4539 ...