React.js同构实践

Posted 微医前端

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了React.js同构实践相关的知识,希望对你有一定的参考价值。

同构:英文表述为 Isomorphic 或 Universal,即编写的 javascript 代码可同时运行于浏览器及 Node.js 两套环境中,用服务端渲染来提升首屏的加载速度,首屏之后的路由由客户端控制,即在用户到达首屏后,整个应用仍是一个 SPA。

为实现同构,需要实现这些功能:

  •  在服务端根据React组件产生html

  •  数据脱水和注水

  •  服务端管理Redux Store

  •  支持浏览器端和服务端获取共同数据源

React服务端渲染HTML

React在浏览器渲染:

importReactDOM from 'react-dom';

ReactDOM.render(<RootComponent/>,document.getElementById('root'))

React在服务端渲染:

importReactDOMServer from 'react-dom/server';

constappHtml = ReactDOMServer.renderToString(<RootComponent/>)

服务端最终产出的是由renderToString返回的HTML字符串,这个节点会被存放在根节点属性 data-react-checksum 中。在浏览器渲染过程中会进行校验,所以为了防止出现闪烁,就必须保证两端的渲染结果是一样的。

为了让两端数据一致,就要涉及“脱水”和“注水”

脱水和注水

服务端交给浏览器的不光要有HTML,还需要有“脱水数据”,也就是在服务端渲染过程中给React组件的输入数据。当浏览器渲染时可以直接根据“脱水数据”来渲染React组件,这个过程叫做“注水”。使用“脱水数据”就是为了保证两端数据一致,同时避免不必要的服务器请求。

“脱水数据”传递至浏览器的方式:

// 使用ejs模板

<!--appHtml上文提到的,有renderToString生成的 -->

<divid="root"><%- appHtml %></div>

<script>

    var DEHYDRATED_STATE = <%-dehydratedState %>

</script>

既然我们使用Redux来管理应用数据,那么脱水数据就应该是来自于Redux的Store,借助于react-redux的Provider帮助,可以让每一个React都从一个Store获取数据

constappHtml = ReactDOMServer.renderToString(

    <Provider>

        <RouterContext {...renderProps}/>

    </Provider>

);

 

constdehydratedState = store.getState()

脱水数据一定不能太大,因为脱水数据要占用网页的大小,数据太大,可能会影响性能,服务器渲染就失去意义了

服务器 Redux Store

在服务器使用Redux,必须要对每个请求都创造一个新的Store,这是和浏览器最大的区别。因为浏览器只是针对一个用户,而服务器会接受很多浏览器请求。

//src/Store.js

constconfigureStore = () => {

    const store = createStore(reducer, {},storeEnhancers);

    store._reducers = originalReducers;

    return store

}

export{configureStore}

src/Store.js导出的不再是Store实例而是一个configureStore函数

注:同构和非同构保持一致性写法就不会出现这方面的差异

服务器路由

React-Router使用3.x版本,升级到最新4.x版本改动很大,后期迭代至最新版本

服务器路由采用React-Router提供的路由,与浏览器配置不同,在浏览器端,整个Router作为一个React组件传递一个ReactDOM的render函数,Router可以自动和URL同步;而服务端的过程,URL对应到路由规则需要使用match函数:

exportconst renderPage = (req, res, assetManifest) => {

  match({routes: routes, location: req.url},function(err, redirect, renderProps) {

    if (err) {

      return res.status(500).send(err.message);

    }

 

    if (redirect) {

      return res.redirect(redirect.pathname +redirect.search);

    }

 

    if (!renderProps) {

      return res.status(404).send('Not Found');

    }

   

    const appHtml =ReactDOMServer.renderToString(

        <Provider>

            <RouterContext {...renderProps}/>

        </Provider>

    );

  });

};

如果项目使用分片,浏览器渲染就需要做对应修改,使用match函数完成匹配,否则,两端产出的HTML会不一致

match({history,routes},(err,redirectLocation,renderProps)=> {

    ReactDOm.render(

        <Router {...renderProps} />,

        domElement

    )

})

可以注意到,到浏览器端match函数确定当前路径的参数用的是history,而不像服务器那样使用的是URL字符串

点击查看完整实例

Link

基于next.js的的React同构网上有对应的例子,这里不做分享!我这里有搭建支持redux,redux-saga的脚手架


以上是关于React.js同构实践的主要内容,如果未能解决你的问题,请参考以下文章

React JS 同构渲染

React.js最佳实践01

React.js 2016 最佳实践

React.js 个人作品集项目实践

奇舞周刊 145 期—— React.js 2016 最佳实践

React.js 项目实践——创建个人作品集网页