搭建React服务端渲染项目知识梳理及总结
本项目github地址 react-koa2-ssr 所用到技术栈 react16.x + react-router4.x + koa2.x 前言前段时间业余做了一个简单的古文网 ,但是项目是使用React SPA 渲染的,不利于SEO,便有了服务端渲染这个需求。后面就想写个demo把整个过程总结一下,同时也加深自己对其的理解,期间由于工作,过程是断断续续 。总之后来就有了这个项目吧。关于服务端渲染的优缺点,vue服务端渲染官方文档讲的最清楚。讲的最清楚。 对于大部分场景最主要还是两点 提高首屏加载速度 和方便SEO.为了快速构建开发环境,这里直接使用create-react-app 和koa2.x生成一个基础项目 。整个项目便是以此作为基点进行开发的,目前也只是完成了最基本的需求, 还有很多Bug 和可以优化的地方, 欢迎交流。 服务端渲染最基本的理论知识梳理首先前后端分别使用create-react-app 和koa2的脚手架快速生成, 然后再将两个项目合并到一起。这样我们省去了webpack的一些繁琐配置 ,同时服务端使用了babel编译。看这个之前 默认已经掌握webpack 和 koa2.x,babel的相关知识。
react 服务端渲染的条件其实可以看 《深入React技术栈》的第七章, 介绍的非常详细。
这两个方法的存在 ,实际上可以把react看做是一个模板引擎。解析jsx语法变成普通的html字符串。 我们可以调用这两个API 实现传入ReactComponent 返回对应的html字符串到客户端。浏览器端接收到这段html以后不会重新去渲染DOM树,只是去做事件绑定等操作。这样就提高了首屏加载的性能。 react-router4.x 和 服务端的路由实现同构。react-router4.x 相对于之前的版本,做了较大的改动。 整个路由变得组件化了。 服务端渲染与客户端渲染的不同之处在于其路由是没有状态的,所以我们需要通过一个无状态的router组件 来包裹APP,通过服务端请求的url来匹配到具体的路由数组和其相关属性。
参考代码如下所示: // 服务端路由配置 import { createServer } from 'http' import React from 'react' import ReactDOMServer from 'react-dom/server' import { StaticRouter } from 'react-router' import App from './App' createServer((req,res) => { const context = {} const html = ReactDOMServer.renderToString( <StaticRouter location={req.url} context={context} > <App/> </StaticRouter> ) if (context.url) { res.writeHead(301,{ Location: context.url }) res.end() } else { res.write(` <!doctype html> <div id="app">${html}</div> `) res.end() } }).listen(3000) And then the client:import ReactDOM from 'react-dom' // 客户端路由配置 import { BrowserRouter } from 'react-router-dom' import App from './App' ReactDOM.render(( <BrowserRouter> <App/> </BrowserRouter> ),document.getElementById('app')) 我们把koa的路由url传入 <StaticRouter />,后者会根据url 自动匹配对应的React组件,这样我们就能实现,刷新页面,服务端返回的对应路由组件与客户端一致。 Redux 服务端同构首先下官方文档做了简单的介绍介绍http://cn.redux.js.org/docs/recipes/ServerRendering.html. 其处理步骤如下:
如下面代码所示。 服务端 <html> <head> <title>Redux Universal Example</title> </head> <body> <div id="root">${html}</div> <script> window.__INITIAL_STATE__ = ${JSON.stringify(finalState)} </script> <script src="/static/bundle.js"></script> </body> </html> 客户端 ... // 通过服务端注入的全局变量得到初始 state const preloadedState = window.__INITIAL_STATE__ // 使用初始 state 创建 Redux store const store = createStore(counterApp,preloadedState) render( <Provider store={store}> <App /> </Provider>,document.getElementById('root') ) 这个基本上就是一个标准的redux同构流程, 其实更多的官方是在给我们提供一种标准化的思路,我们可以顺着这个做更多的优化。
我觉得优点是比较明确直观,直接在路由层就把这个事情解决了。
本项目采用了第二种方案,先看一下代码: /** * 渲染服务端路由 */ module.exports.render = async(ctx,next) =>{ const { store,history} = getCreateStore(ctx); const branch = matchRoutes(router,ctx.req.url); const promises = branch.map(({route}) => { const fetch = route.component.fetch; return fetch instanceof Function ? fetch(store) : Promise.resolve(null) }); await Promise.all(promises).catch((err)=>{ console.log(err); }); const html = ReactDOMServer.renderToString( <Provider store={store}> <StaticRouter location={ctx.url} context={{}}> <App/> </StaticRouter> </Provider> ) let initState=store.getState(); const body = layout(html,initState); ctx.body =body; } 对应容器组件提供了一个静态的fetch方法 class Home extends Component { ... static fetch(store){ return store.dispatch(fetchBookList({page:1,size:20})) } 这是我们的 actions /** * 获取书籍目录 * @param {*} param */ export const fetchBookList = (params) => { return async (dispatch,getState) => { await axios.get(api.url.booklist,{ params: params }).then((res) => { dispatch(booklist(res.data.result)); }).catch((err) => { }) } } 首先我们通过 matchRoutes 拿到当前路由下所有的路由,再对其遍历得到有关一个异步方法的Promise数组,这里我们所谓的异步方法就是actions中的异步方法。由于我们在服务端也初始化的store所以我们可以直接在服务端调用actions,这里我们需要给容器组件的static方法传入store,这样我们就可以通过 到这一步我们已经可以实现刷新页面异步数据服务端处理,不刷新页面前端处理,一个基本的同构方案主体就出来了,剩下的就是一些优化项和一些项目定制性的东西了。 服务端页面分发对于服务器而言不仅会收到前端路由的请求还会收到各种其他静态资源的请求 其他写这个demo看了很多的github项目以及相关文章,这些资料对本项目有很大的启发 Vue.js 服务器端渲染指南 react-server beidou react-ssr-optimization React-universal-ssr fairy D2 - 打造高可靠与高性能的React同构解决方案 Egg + React 服务端渲染开发指南 服务端渲染与 Universal React App React同构直出优化总结 React移动web极致优化 https://github.com/joeyguo 总结我们知道服务端渲染的 本项目github地址 https://github.com/yangfan0095/react-koa2-ssr 以上です (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |