简要介绍:为了SEO和加快首屏加载速度,React提供了服务端渲染(Server Side Render)。本文结合express,来介绍一下React16.x中的SSR。
本例代码:https://github.com/forthealllight/react16.0-ssr
一、为什么要SSR
单页应用将UI层和内容都由javascript来渲染,搜索引擎或网页爬虫需要完成的HTML结构,因此单页应用如果只在客户端渲染,不利于SEO,此外尽管我们可以通过按需加载的形式来减少首页加载的js,但是通过js来渲染DOM的时候还是会有一定的时间延迟。
因此SSR解决的问题有两个:
SEO
加速首屏加载
在React和Vue等前端框架中,SSR的本质就是由服务端执行渲染,直接将渲染结果以HTML结构的形式返回给客户端。也就是将Virtual DOM转化成字符串的形式返回给客户端。
二、React15.x中的SSR
在React15.x中,有两个方法来处理SSR:
renderToString
renderToStaticMarkup
这两个方法都是在react-dom/server中提供的,用来在服务端将virtual dom渲染成字符串。
(1)相同点
renderToString和renderToStaticMarkup都接受一个参数,这个参数是react的组件,返回一段HTML字符串。
renderToString(react element):string
renderToStaticMarkup(react element):string
此外react-dom中给浏览器端提供了一个render方法,render方法将react组件,添加到真实的DOM节点中。render实现的就是浏览器端渲染。
归类一下:
服务端渲染:renderToString、renderToStaticMarkup——>string
客户端渲染:render——>HTML结构
(2)SSR实现
下面我们以renderToString为例,通过express来实现一个服务端渲染的例子。
首先node最新版本为8.9.3,还不支持es6语法,同时为了使node支持jsx,我们需要安装babel,本文为了方便,采用了babel-cli。
首先安装babel-cli:
npm install -d babel-cli
接着安装presets:
npm install -d babel-preset-latest babel-preset-stage-0 babel-preset-react
接着我们在script中:
"start":"babel-node ./server/server.js --presets es2015,stage-0,react"
最后,就可以通过 npm run start的方式实现启动server.js,server.js是经过babel处理,可以支持ES6和jsx.
在server.js中,我们利用了express的路由和中间件模块。
let express=require('express');
let app=express();
import React from 'react';
import {renderToString,renderToStaticMarkup} from 'react-dom/server';
import HomePage from '../src/components/homepage/index.js';
var server=app.listen(8080,()=>{
var host=server.address().address;
var port=server.address().port;
console.log('server is start at',host,port);
});
//static
app.use('/dist',express.static('dist'));
app.get('/',(req,res)=>{
res.write('Hello HomePage ');
res.write('');
res.write(renderToString( ));
res.write('');
res.write('');
res.write('');
})
结构很简单,因为返回的html页面要加载静态资源,因此我们在上述的代码中还使用了express内置的静态文件模块express.static.
最后,通过npm start就能启动本地服务器,在浏览器中打开:
http://localhost:8080/ 就能看到我们SSR的例子。
(3)renderToString和renderToStaticMarkup的区别
renderToString:渲染的结果是带有data-reactid属性的,此时,在服务端的基础上,客户端的render不会重新渲染,只会执行组件componetDidmout中的业务,以及绑定事件等等。
renderToStaticMarkup:渲染的结果是不带有data-reactid属性的,此时不管服务端有没有渲染,在客户端中都会重新渲染该组件。
比如在renderToString的关于HomePage的返回HTML字符串结果为:
<h1 data-reactroot>Home Pageh1>
而在renderToStaticMarkup中,关于HomePage的返回HTML字符串结果为:
<h1>Home Pageh1>
三、React16.x中的SSR
(1)hydrate
在React16.x中,在客户端渲染的render的方法的基础上,增加了一个新的方法hydrate.
简单来说,如果在仅在客户端呈现内容,那么使用render方法就已经足够,如果客户端要在服务端的基础上进行渲染,那么可以使用hydrate.
使用的方法和render一样:
import {hydrate} from 'react-dom';
hydrate(<HomePage/>,document.getElementById('app'));
运行后发现提示:
Warning: render(): Calling ReactDOM.render() to hydrate server-rendered markup will stop working in React v17. Replace the ReactDOM.render() call with ReactDOM.hydrate() if you want React to attach to the server HTML.
说明React16.x中,客户端“水合”服务端,是兼容之前的render方法的,之后的版本中会移除render方法,完全用hydrate来代替。
hydrate方法,解决的是如何复用server端,ReactDOMServer的结果。
(2)stream
此外React16.x中,针对renderToString和renderToStaticMarkup提供了stream的方法:
这两个方法同样接受的参数为react element,但是返回的不是HTML字符串,而是一个可读流。
最后给出完整代码的地址,直接npm start就可以运行:
https://github.com/forthealllight/react16.0-ssr
四、注意事项
如果不用babel-cli的方法,来babel node文件,用webpack的话可能会报一下错误:
RROR in ./node_modules/destroy/index.js
Module not found: Error: Can't resolve 'fs' in 'C:\Users\yuxl\Desktop\react-Scaffold-master\node_modules\destroy'
@ ./node_modules/destroy/index.js 14:17-30
@ ./node_modules/send/index.js
@ ./node_modules/express/lib/response.js
@ ./node_modules/express/lib/express.js
@ ./node_modules/express/index.js
@ ./src/server.js
ERROR in ./node_modules/etag/index.js
Module not found: Error: Can't resolve 'fs' in 'C:\Users\yuxl\Desktop\react-Scaffold-master\node_modules\etag'
@ ./node_modules/etag/index.js 22:12-25
@ ./node_modules/express/lib/utils.js
@ ./node_modules/express/lib/application.js
@ ./node_modules/express/lib/express.js
@ ./node_modules/express/index.js
@ ./src/server.js
ERROR in ./node_modules/express/lib/view.js
Module not found: Error: Can't resolve 'fs' in 'C:\Users\yuxl\Desktop\react-Scaffold-master\node_modules\express\lib'
@ ./node_modules/express/lib/view.js 18:9-22
@ ./node_modules/express/lib/application.js
@ ./node_modules/express/lib/express.js
@ ./node_modules/express/index.js
@ ./src/server.js
ERROR in ./node_modules/send/index.js
Module not found: Error: Can't resolve 'fs' in 'C:\Users\yuxl\Desktop\react-Scaffold-master\node_modules\send'
@ ./node_modules/send/index.js 23:9-22
@ ./node_modules/express/lib/response.js
@ ./node_modules/express/lib/express.js
@ ./node_modules/express/index.js
@ ./src/server.js
ERROR in ./node_modules/send/node_modules/mime/mime.js
Module not found: Error: Can't resolve 'fs' in 'C:\Users\yuxl\Desktop\react-Scaffold-master\node_modules\send\node_modules\mime'
@ ./node_modules/send/node_modules/mime/mime.js 2:9-22
@ ./node_modules/send/index.js
@ ./node_modules/express/lib/response.js
@ ./node_modules/express/lib/express.js
@ ./node_modules/express/index.js
也就是webpack在打包的时候找不到 node自带的模块,比如fs等,解决的方法是在webpack的配置文件里面增加:
target:'node'
参考文章:https://hackernoon.com/whats-new-with-server-side-rendering-in-react-16-9b0d78585d67