• React Redux Sever Rendering实战


    # 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)

  • 相关阅读:
    安装node和npm
    安装git
    常用软件
    vscode常用插件
    git生成ssh key
    04.接口初始化规则与类加载器准备阶段和初始化阶段的意义
    03.编译期常量与运行期常量的区别与数组创建本质分析
    02.常量的本质含义与反编译及助记符
    01.类加载,连接与初始化过程
    HTTP 状态码大全
  • 原文地址:https://www.cnblogs.com/cllgeek/p/6065742.html
Copyright © 2020-2023  润新知