Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
forthealllight opened this issue May 20, 2018 · 0 comments
Open

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

forthealllight opened this issue May 20, 2018 · 0 comments

Comments

@forthealllight
Copy link
Owner

forthealllight commented May 20, 2018

简要介绍:为了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的例子。

11

(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'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant