本文的demo仓库在 https://github.com/qiqingjin/blog/tree/master/React_Redux/,喜欢请star哟~
前面的话
React 16开始重视服务器端渲染,也就是SSR,不再严格对比checksum,不知道你的应用升级了么。
Webpack 4的支持主要看依赖的plugin和loader,目前主流plugin已经支持,可以升级咯。
demo地址:https://github.com/qiqingjin/blog/tree/master/React_Redux/demos,使用了React,Redux和Webpack,服务器是Koa,你可以clone这个仓库,使用 https://github.com/qiqingjin/blog/blob/master/React_Redux/README.md 这个步骤进行本地启动。
基本思想
简单来说,我要做的事情就是,写一套react页面,服务器端和浏览器端都可以把它们转换成html页面。当用户请求某些页面时,例如:首页,先进行服务器端渲染,然后返回html页面。浏览器会把服务器端html与浏览器端渲染的html页面对比,并尽量复用服务器端返回的html,展示到浏览器上。注意,React 16不再使用checksum进行严格校验。
面临的问题
- 服务器端渲染不会执行ajax请求,如何进行数据请求
- 不同的路由,在浏览器端会请求不同的数据,如何在服务器端根据路由来进行数据处理
数据请求
react-dom/server
提供了一个renderToString
方法,可以把react的虚拟dom,也就是react component的类,转换成html。我们只要在调用这个方法之前,提前执行需要在浏览器端执行的请求就好,看代码:
// demos/server/server.js
const {data: todosData} = await axios.get('http://localhost:667/rest/todos');
然后需要获取react组件,我这里使用koa
,node
端不支持import
等方法,需要用webpack
把react组件编译,注意,服务器端编译的config配置和浏览器端编译的config略有不同,请看demos/client/webpack.config.js
和demos/client/webpack.config.ssr.js
。获取react组件的代码:
// demos/server/server.js
const indexBundle = require('./dist/main.ssr');
const indexApp = indexBundle.__esModule ? indexBundle.default : indexBundle;
获取react组件后,需要把数据注入到redux的createStore
方法中,再传给react组件,保证react组件中的store
有dispatch
等方法。
// demos/server/server.js
const store = indexApp.createStore(initData);
const instance = indexApp.createApp(store);
const todosStr = ReactDOMServer.renderToString(instance);
最后,需要把数据放到window
对象上,让浏览器端用来渲染浏览器端html,然后与服务器端html对比,尽量复用。
// demos/server/server.js
const syncScript = `<script id="server-data">window._SERVER_DATA=${JSON.stringify(initData)}</script>`;
const $ = await loadHTMLTemplate(path.resolve(__dirname, '../client/dist/index.html'));
$('#app').html(todosStr);
$('head').append(syncScript);
路由处理
这个地方目前我看到的处理方法有:
1. 使用react-router
的match
方法,这个方案比较常见
2. 把页面分成多个thunk
,并绑定到客户的的路由文件中,在renderToString
之前dispatch
这些异步方法,从而将数据更新到redux的store中,请参考
这里我并没有写在我的代码中,如果你想尝试,可以clone
我的仓库,自己进行优化。