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同构实践的主要内容,如果未能解决你的问题,请参考以下文章