# React Redux Sever Rendering(Isomorphic JavaScript)

![React Redux Sever Rendering(Isomorphic)入门](http://obl1r1s1x.bkt.clouddn.com/isomorphic-javascript.png)

## 前言
由于可能有些读者没听过 [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 只处理简单的是否显示或是元素迭代的状况,如下图所示:

![React Redux Sever Rendering(Isomorphic)入门](http://obl1r1s1x.bkt.clouddn.com/isomorphic-api.png)

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

![React Redux Sever Rendering(Isomorphic)入门](http://obl1r1s1x.bkt.clouddn.com/client-mvc.png)

事实上,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

## 专案成果截图
![image](http://obl1r1s1x.bkt.clouddn.com/isomorphic-app.png)

## 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实战的更多相关文章

  1. React前端有钱途吗?《React+Redux前端开发实战》学起来

    再不学React就真的跟不上大前端的形式了,目前几乎所有前端的招聘条件都是精通React者优先,看看拉勾网的React薪资,都是15K-20K,这个暑假,必须动起来了. 如果你熟悉JavaScript ...

  2. 《React+Redux前端开发实战》笔记3:基于Webpack构建的Hello World案例(下)

    2.使用React编码 下面正式开始使用React来编写前端代码. (1)npm安装react和react-dom: npm install react react-dom -S (2)用下面代码替换 ...

  3. 《React+Redux前端开发实战》笔记2:基于Webpack构建的Hello World案例(上)

    这次搭建分为两部分:一部分是前期必要配置,一部分是开发React代码. [基于Webpack的React Hello World项目] 1.前期必要配置 (1)首先要确保读者的开发设备上已经安装过No ...

  4. 《React+Redux前端开发实战》笔记1:不涉及React项目构建的Hello World案例

    本小节实现一个不涉及项目构建的Hello World. [React的第一个Hello World网页] 源码地址:https://jsfiddle.net/allan91/2h1sf0ky/8/ & ...

  5. 【原】react+redux实战

    摘要:因为最近搞懂了redux的异步操作,所以觉得可以用react+redux来做一个小小的项目了,以此来加深一下印象.切记,是小小的项目,所以项目肯定是比较简单的啦,哈哈. 项目效果图如图所示:(因 ...

  6. React+Redux开发实战项目【美团App】,没你想的那么难

    README.md 前言 开始学习React的时候,在网上找了一些文章,读了官网的一些文档,后来觉得React上手还是蛮简单的, 然后就在网上找了一个React实战的练手项目,个人学完之后觉得这个项目 ...

  7. React.js 入门与实战之开发适配PC端及移动端新闻头条平台课程上线了

    原文发表于我的技术博客 我在慕课网的「React.js 入门与实战之开发适配PC端及移动端新闻头条平台」课程已经上线了,文章中是目前整个课程的大纲,以后此课程还会保持持续更新,此大纲文档也会保持更新, ...

  8. webpack+react+redux+es6开发模式

    一.预备知识 node, npm, react, redux, es6, webpack 二.学习资源 ECMAScript 6入门 React和Redux的连接react-redux Redux 入 ...

  9. react+redux教程(六)redux服务端渲染流程

    今天,我们要讲解的是react+redux服务端渲染.个人认为,react击败angular的真正“杀手锏”就是服务端渲染.我们为什么要实现服务端渲染,主要是为了SEO. 例子 例子仍然是官方的计数器 ...

随机推荐

  1. JAVA中的数据结构——集合类(序):枚举器、拷贝、集合类的排序

    枚举器与数据操作 1)枚举器为我们提供了访问集合的方法,而且解决了访问对象的“数据类型不确定”的难题.这是面向对象“多态”思想的应用.其实是通过抽象不同集合对象的共同代码,将相同的功能代码封装到了枚举 ...

  2. (原创)spring mvc和jersey rest 组合使用时单例对像实例化两次的BUG及解决办法

    项目中没用spring 的restTemplate 而是采用 jersey来做rest 的实现,一直用着,也没发现有什么不对,后来加入了,以quartz用硬编码方式实现,结果启动项目的时候报错 ,具体 ...

  3. 现代程序设计——homework-07

    1.写在前面 不得不很惭愧地说,在看这些博客之前,我对C++的了解仅限于上过一门特别水的关于C++的公选课.一门只有五节课的专业选修课,写过一点点符合C++语法语法规则的类C程序,偶尔在论坛.博客中看 ...

  4. 03 javadoc

    javadoc从程序源代码中抽取类.方法.成员等注释形成一个和源代码配套的API帮助文档 1.标签.命令格式: 2.使用方式: 2.1 dos命令行格式:javadoc XXX.java 2.2 ec ...

  5. Spring SimpleJdbcTemplate Querying examples

    Here are few examples to show how to use SimpleJdbcTemplate query() methods to query or extract data ...

  6. Define custom @Required-style annotation in Spring

    The @Required annotation is used to make sure a particular property has been set. If you are migrate ...

  7. 微信读书 iOS 性能优化总结

    微信读书作为一款阅读类的新产品,目前还处于快速迭代,不断尝试的过程中,性能问题也在业务的不断累积中逐渐体现出来.最近的 1.3.0 版本发布后,关于性能问题的用户反馈逐渐增多,为此,团队开始做一些针对 ...

  8. Oracle创建dblink报错:ORA-01017、ORA-02063解决

    Oracle环境:oracle 10.2.0.1 创建的 public dblink 连接oracle 11.2.0.3 ORA-01017: invalid username/password; l ...

  9. Android中回调接口的使用

    MainActivity如下: package cn.testcallback; import android.os.Bundle; import android.app.Activity; /** ...

  10. MySQL Update语句用法

    用一个表的某列值更新另外一个表的某列值的sql语句: update tableA a innner join tableB b on a.column_1 = b.column_1 set a.col ...