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

yuxiaoliang發表於2018-07-02

我的部落格原文地址:原文地址

簡要介紹:為了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'
複製程式碼

相關文章