react.js在服务器端渲染有啥好处?渲染是怎么个流程
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了react.js在服务器端渲染有啥好处?渲染是怎么个流程相关的知识,希望对你有一定的参考价值。
有些回答中提到CPU负载和node.js效率问题。服务器端渲染固然耗CPU,但可以使用服务器端缓存的方式解决,并不是每个用户访问都需要重新渲染一
遍。而且服务器端渲染甚至可以潜在地增加服务器效率(这点在参考资料第二个里有提到,不过是纯英文的,我有空会翻译下)。
2. 服务器端和客户端可以共享某些代码,避免重复定义。这样可以使结构更清晰,增加可维护性
3. 首次加载页面的速度加快。客户端渲染的一个缺点是,当用户第一次进入站点,此时浏览器中没有缓存,需要下载代码后在本地渲染,时间较长。而服务器渲染则是,用户在下载的已经是渲染好的页面了,打开速度比本地渲染快。
4. SEO。服务器端渲染可以让搜索引擎更容易读取页面的meta信息以及其他SEO相关信息,大大增加网站在搜索引擎中的可见度。
其实并不一定要争个好坏,服务器端和客户端渲染各有各的优缺点。建议根据实际需求,在某些页面使用服务器渲染,某些页面使用客户端渲染,以达到最佳解决方案。
我的服务器用的是小鸟云的,挺不错的。
提升性能是需要再浏览器端的性能提升还是服务端的 性能提升,是两个概念,服务端渲染会给服务端造成一定的压力,减轻客户端的压力;好处:在整个页面级别的应用会使得浏览器在解析dom完成之后马上有东西可以渲染。再者就是对seo比较友好一些;
渲染的流程主要是:
准备数据,一般从数据库或外部API获得 (一般要先 render React 一次,去触发所需的API)
数据和React结合生成html Markup
除了把HMTL Markup输出外, 还要把'State'输出,这要在客户端才能保留'State' 参考技术B
react.js在服务器端渲染好处:
提升性能是需要再浏览器端的性能提升还是服务端的 性能提升,是两个概念,服务端渲染会给服务端造成一定的压力,减轻客户端的压力;好处:在整个页面级别的应用会使得浏览器在解析dom完成之后马上有东西可以渲染。再者就是对seo比较友好一些;
渲染的流程主要是:
准备数据,一般从数据库或外部API获得 (一般要先 render React 一次,去触发所需的API)
数据和React结合生成HTML Markup
除了把HMTL Markup输出外, 还要把'State'输出,这要在客户端才能保留'State'
有些回答中提到CPU负载和node.js效率问题。服务器端渲染固然耗CPU,但可以使用服务器端缓存的方式解决,并不是每个用户访问都需要重新渲染一
遍。而且服务器端渲染甚至可以潜在地增加服务器效率(这点在参考资料第二个里有提到,不过是纯英文的,我有空会翻译下)。
2. 服务器端和客户端可以共享某些代码,避免重复定义。这样可以使结构更清晰,增加可维护性
3. 首次加载页面的速度加快。客户端渲染的一个缺点是,当用户第一次进入站点,此时浏览器中没有缓存,需要下载代码后在本地渲染,时间较长。而服务器渲染则是,用户在下载的已经是渲染好的页面了,打开速度比本地渲染快。
4. SEO。服务器端渲染可以让搜索引擎更容易读取页面的meta信息以及其他SEO相关信息,大大增加网站在搜索引擎中的可见度。
其实并不一定要争个好坏,服务器端和客户端渲染各有各的优缺点。建议根据实际需求,在某些页面使用服务器渲染,某些页面使用客户端渲染,以达到最佳解决方案。 参考技术D 好处分为两方面:
首先,是服务器渲染的好处,很明显,服务器渲染肯定比客户端渲染,首屏加载的速度要快一些。配合bigpipe这种网页加载模型速度会显著提升。
其次,服务器渲染使用react的好处是,服务端和网页端可以共享同一套jsx代码。不使用js的话,通常服务器需要自己使用后端语言拼接字符串,或者php,jsp,asp这种代码/html混写的风格,或者特定平台的模板引擎库去输出页面。无论,那种,服务端都要自己实现一段代码。而前端页面js也要写一套代码用来渲染ajax的异步数据,其实这部分工作是重复的。如果使用nodejs开发,那么前后端因为都使用js语言,因为前后端同构可以避免这种重复代码。
总结一下:1.提升了团队的工作效率(想想前后端配合的各种坑)。2.代码复用度更好。3.网站性能更好。
基于 React.js 和 Node.js 的SSR实现方案
基础概念
SSR:即服务端渲染(Server Side Render)
传统的服务端渲染可以使用Java,php 等开发语言来实现,随着 Node.js 和相关前端领域技术的不断进步,前端同学也可以基于此完成独立的服务端渲染。过程:浏览器发送请求 -> 服务器运行 React代码生成页面 -> 服务器返回页面 -> 浏览器下载HTML文档 -> 页面准备就绪
即:当前页面的内容是服务器生成好给到浏览器的。对应CSR:即客户端渲染(Client Side Render)
过程:浏览器发送请求 -> 服务器返回空白 HTML(HTML里包含一个root节点和js文件) -> 浏览器下载js文件 -> 浏览器运行react代码 -> 页面准备就绪
即:当前页面的内容是JavaScript 渲染出来。如何区分页面是否服务端渲染:
右键点击 -> 显示网页源代码,如果页面上的内容在HTML文档里,是服务端渲染,否则就是客户端渲染。对比
CSR:首屏渲染时间长,react代码运行在浏览器,消耗的是浏览器的性能
SSR:首屏渲染时间短,react代码运行在服务器,消耗的是服务器的性能
为什么要用服务端渲染
首屏加载时间优化,由于SSR是直接返回生成好内容的HTML,而普通的CSR是先返回空白的HTML,再由浏览器动态加载JavaScript脚本并渲染好后页面才有内容;所以SSR首屏加载更快、减少白屏的时间、用户体验更好。
SEO (搜索引擎优化),搜索关键词的时候排名,对大多数搜索引擎,不识别JavaScript 内容,只识别 HTML 内容。
(注:原则上可以不用服务端渲染时最好不用,所以如果只有 SEO 要求,可以用预渲染等技术去替代)
构建一个服务端渲染的项目
(1) 使用 Node.js 作为服务端和客户端的中间层,承担 proxy代理,处理cookie等操作。
(2) hydrate 的使用:在有服务端渲染情况下,使用hydrate代替render,它的作用主要是将相关的事件注水进HTML页面中(即:让React组件的数据随着HTML文档一起传递给浏览器网页),这样可以保持服务端数据和浏览器端一致,避免闪屏,使第一次加载体验更高效流畅。
1 ReactDom.hydrate(<App />, document.getElementById('root'));
(3) 服务端代码webpack编译:通常会建一个webpack.server.js文件,除了常规的参数配置外,还需要设置target参数为'node'。
1const serverConfig = {
2 target: 'node',
3 entry: './src/server/index.js',
4 output: {
5 filename: 'bundle.js',
6 path: path.resolve(__dirname, '../dist')
7 },
8 externals: [nodeExternals()],
9 module: {
10 rules: [{
11 test: /\.js?$/,
12 loader: 'babel-loader',
13 exclude: [
14 path.join(__dirname, './node_modules')
15 ]
16 }
17 ...
18 ]
19 }
20 (此处省略样式打包,代码压缩,运行坏境配置等等...)
21 ...
22};
(4) 使用react-dom/server下的 renderToString方法在服务器上把各种复杂的组件和代码转化成 HTML 字符串返回到浏览器,并在初始请求时发送标记以加快页面加载速度,并允许搜索引擎抓取页面以实现SEO目的。
1const render = (store, routes, req, context) => {
2 const content = renderToString((
3 <Provider store={store}>
4 <StaticRouter location={req.path} context={context}>
5 <div>
6 {renderRoutes(routes)}
7 </div>
8 </StaticRouter>
9 </Provider>
10 ));
11 return `
12 <html>
13 <head>
14 <title>ssr</title>
15 </head>
16 <body>
17 <div id='root'>${content}</div>
18 <script src='/index.js'></script>
19 </body>
20 </html>
21 `;
22}
23app.get('*', function (req, res) {
24 ...
25 const html = render(store, routes, req, context);
26 res.send(html);
27});
与 renderToString类似功能的还有:
i. renderToStaticMarkup:区别在于renderToStaticMarkup 渲染出的是不带data-reactid的纯HTML,在JavaScript加载完成后因为不认识之前服务端渲染的内容导致重新渲染(可能页面会闪一下)。
ii. renderToNodeStream:将React元素渲染为其初始HTML,返回一个输出HTML字符串的可读流。
iii. renderToStaticNodeStream:与renderToNodeStream此类似,除了这不会创建React在内部使用的额外DOM属性,例如data-reactroot。
(5) 使用redux 承担数据准备,状态维护的职责,通常搭配react-redux, redux-thunk(中间件:发异步请求用到action)使用。(本猿目前使用比较多是就是Redux和Mobx,这里以Redux为例)。
A. 创建store(服务器每次请求都要创建一次,客户端只创建一次):
1const reducer = combineReducers({
2 home: homeReducer,
3 page1: page1Reducer,
4 page2: page2Reducer
5});
6
7export const getStore = (req) => {
8 return createStore(reducer, applyMiddleware(thunk.withExtraArgument(serverAxios(req))));
9}
10
11export const getClientStore = () => {
12 return createStore(reducer, window.STATE_FROM_SERVER, applyMiddleware(thunk.withExtraArgument(clientAxios)));
13}
B. action: 负责把数据从应用传到store,是store数据的唯一来源
1export const getData = () => {
2 return (dispatch, getState, axiosInstance) => {
3 return axiosInstance.get('interfaceUrl/xxx')
4 .then((res) => {
5 dispatch({
6 type: 'HOME_LIST',
7 list: res.list
8 })
9 });
10 }
11}
C. reducer:接收旧的state和action,返回新的state,响应actions并发送到store。
1export default (state = { list: [] }, action) => {
2 switch(action.type) {
3 case 'HOME_LIST':
4 return {
5 ...state,
6 list: action.list
7 }
8 default:
9 return state;
10 }
11}
12export default (state = { list: [] }, action) => {
13 switch(action.type) {
14 case 'HOME_LIST':
15 return {
16 ...state,
17 list: action.list
18 }
19 default:
20 return state;
21 }
22}
D. 使用react-redux的connect,Provider把组件和store连接起来
Provider 将之前创建的store作为prop传给Provider
1const content = renderToString((
2 <Provider store={store}>
3 <StaticRouter location={req.path} context={context}>
4 <div>
5 {renderRoutes(routes)}
6 </div>
7 </StaticRouter>
8 </Provider>
9));
connect([mapStateToProps],[mapDispatchToProps],[mergeProps], [options])接收四个参数,常用的是前两个属性
mapStateToProps 函数允许我们将store中的数据作为 props 绑定到组件上
mapDispatchToProps 将action作为props绑定到组件上
1 connect(mapStateToProps(),mapDispatchToProps())(MyComponent)
(6) 使用react-router承担路由职责
服务端路由不同于客户端,它是无状态的。React 提供了一个无状态的组件StaticRouter,向StaticRouter传递当前URL,调用ReactDOMServer.renderToString() 就能匹配到路由视图。
服务端
1import { StaticRouter } from 'react-router-dom';
2import { renderRoutes } from 'react-router-config'
3import routes from './router.js'
4
5<StaticRouter location={req.path} context={{context}}>
6{renderRoutes(routes)}
7</StaticRouter>
8
浏览器端
1import { BrowserRouter } from 'react-router-dom';
2import { renderRoutes } from 'react-router-config'
3import routes from './router.js'
4
5<BrowserRouter>
6 {renderRoutes(routes)}
7</BrowserRouter>
8
1const routes = [{ component: Root,
2 routes: [
3 { path: '/',
4 exact: true,
5 component: Home,
6 loadData: Home.loadData
7 },
8 { path: '/child/:id',
9 component: Child,
10 loadData: Child.loadData
11 routes: [
12 path: '/child/:id/grand-child',
13 component: GrandChild,
14 loadData: GrandChild.loadData
15 ]
16 }
17 ]
18}];
1import { matchRoutes } from 'react-router-config'
2 const loadBranchData = (location) => {
3 const branch = matchRoutes(routes, location.pathname)
4
5 const promises = branch.map(({ route, match }) => {
6 return route.loadData
7 ? route.loadData(match)
8 : Promise.resolve(null)
9 })
10
11 return Promise.all(promises)
12}
(7) 写组件注意代码同构(即:一套React代码在服务端执行一次,在客户端再执行一次)
由于服务器端绑定事件是无效的,所以服务器返回的只有页面样式(&注水的数据),同时返回JavaScript文件,在浏览器上下载并执行JavaScript时才能把事件绑上,而我们希望这个过程只需编写一次代码,这个时候就会用到同构,服务端渲染出样式,在客户端执行时绑上事件。
优点: 共用前端代码,节省开发时间
弊端: 由于服务器端和浏览器环境差异,会带来一些问题,如document等对象找不到,DOM计算报错,前端渲染和服务端渲染内容不一致等;前端可以做非常复杂的请求合并和延迟处理,但为了同构,所有这些请求都在预先拿到结果才会渲染。
作者简介
朵拉,铜板街前端开发工程师,2015年8月加入团队,目前主要负责运营侧APP端项目开发。
以上是关于react.js在服务器端渲染有啥好处?渲染是怎么个流程的主要内容,如果未能解决你的问题,请参考以下文章