React16.x中的服务端渲染(SSR)

3,338 阅读5分钟

我的博客原文地址:原文地址

简要介绍:为了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('<!DOCTYPE html><html><head><title>Hello HomePage</title></head><body>');
  res.write('<div id="app">');
  res.write(renderToString(<HomePage/>));
  res.write('</div></body>');
  res.write('<script type="text/javascript" src="../dist/vendor.bundle.js"></script><script type="text/javascript" src="../dist/js/app.js"></script>');
  res.write('</html>');
})

结构很简单,因为返回的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 Page</h1>

而在renderToStaticMarkup中,关于HomePage的返回HTML字符串结果为:

<h1>Home Page</h1>

三、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的方法:

  • renderToNodeStream
  • renderToStaticNodeStream

这两个方法同样接受的参数为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'