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. 例子 例子仍然是官方的计数器 ...
随机推荐
- Tkinter教程之Text(2)篇
本文转载自:http://blog.csdn.net/jcodeer/article/details/1811347 '''Tkinter教程之Text(2)篇''''''6.使用tag来指定文本的属 ...
- 第二百三十八天 how can I 坚持
最近睡觉,老是梦到死亡,多么可怕啊.感觉好虚幻. spring事务管理,框架搭建. 看着没多少事,最起来感觉好多啊. 梳理下最近爬过的山,时间久了会忘,反正上周没爬,下雪了. 10月18号-香山,11 ...
- BestCoder Round #70 Jam's math problem(hdu 5615)
Problem Description Jam has a math problem. He just learned factorization. He is trying to factorize ...
- 关于ssh和ajax小小总结
我是相当的不专业,写给自己看.有什么错误的话,说出来将感激不尽. 有两种方法异步传输 1.通过json字符串: jsp中这么写: $.getJSON( "details_ajaxActio ...
- 33条C#、.Net经典面试题目及答案[zt]
33条C#..Net经典面试题目及答案[zt] 本文集中了多条常见的C#..Net经典面试题目例如“.NET中类和结构的区别”.“ASP.NET页面之间传递值的几种方式?”,并简明扼要的给出了答案,希 ...
- mysql与java数据类型对照
类型名称 显示长度 数据库类型 JAVA类型 JDBC类型索引(int) 描述 VARCHAR L+N VARCHAR java.lang.String 12 CHAR N CHAR java.lan ...
- nginx缓存优先级(缓存问题者必看)
接触nginx的兄弟或多或少都有遇到缓存问题,要么是nginx为什么不缓存,要么就是nginx缓存很快就失效等等问题,在网上找了一遍nginx缓存优先级的文章,大家可以参考下. 架构图client端 ...
- Tomcat7中配置Oracle 11g数据库DBCP连接池
将 ojdbc6.jar tomcat-jdbc-7.0.37.jar 拷贝到工程的WEB-INF\lib 下面 一.在Tomcat的配置文件Tomca ...
- iis 启用父目录路径访问
今天公司有个客户保修网站后台无法访问,我查看了源代码,发现ASP代码本身并没有什么问题.而且我下到本地能够访问.就是在网上不能正常连接,显示入下错误: Server.MapPath() 错误 'ASP ...
- 史上最全!信息安全入门指南<转>
以下所列出的链接均为在线文档,有志于信息安全的爱好者可由此作为入门指南. 背景知识 常规知识 Sun认证-Solaris 9&10安全管理员学习指南 PicoCTF资料 应用软件安全 OWAS ...