使用react這麼久 ,都是基于用戶端渲染,閑來無事,想去玩玩基于react的伺服器端渲染。
用戶端渲染的問題:
- 使用者初次通路的體驗不好
- 對 SEO 不友好
用戶端渲染的頁面通常是很簡潔:
<!doctype html>
<html>
<head>
<title>這是一個單頁面應用</title>
</head>
<body>
<div id='root'></div>
<script src='app.js'></script>
</body>
</html>
通路該頁面,浏覽器便開始解析 HTML 并加載 app.js 檔案,加載完成浏覽器執行 js,接着 <div id='root'></div> 下會生出許多節點,并且具備互動能力。
但是這裡會有兩個問題:
- 在 js 檔案加載完成并執行以前,我們隻看到一片空白,
- 一旦 js 檔案中有錯誤,也可能導緻頁面空白
無論哪種情況,對使用者來說,都是糟糕的體驗。
而服務端渲染能改善使用者的體驗:
- 使用者能馬上看到頁面内容 - 而不是等待 js 檔案下載下傳、執行完成後才能看到,
- 哪怕 js 中有錯誤,也隻會導緻頁面互動問題,而不是一片空白。
在seo層面,我們希望搜尋引擎能夠抓取完整的HTML檔案,而不是上述中的<div id='root'></div>,伺服器端渲染則能夠提供完整的 HTML 内容給搜尋引擎,對 SEO 更友好。
伺服器端渲染流程
傳統的伺服器端渲染大緻是一個這樣的流程:
- 從背景資料中讀取資訊
- 編譯模闆生成HTML代碼
- 傳回HTML給用戶端
- 浏覽器解析html并下載下傳html頁面中的腳本檔案然後執行
React.js的伺服器端渲染也是如此,隻不過這裡的模闆換成了react元件,js事件的綁定由React.js 操作。
伺服器端如何渲染React.js
我們将使用 ReactDOMServer 提供的 renderToString 方法在伺服器端渲染 React.js 元件。下面将簡單的搭建一個基于React.js的伺服器端渲染的小demo。
$ mkdir react-server-render
$ cd react-server-render
$ npm init -y
$ npm i react react-dom --save
在項目目錄中新增index.html檔案
<!doctype html>
<html>
<head>
<title>React 伺服器端渲染</title>
</head>
<body>
<div id='root'></div>
<script src='dist/main.js'></script>
</body>
</html>
然後在react-server-render 下建立一個 src 目錄,在 src 下建立 index.js 以及 App.js,兩個檔案的内容分别如下:
index.js:
const React = require('react')
const ReactDOM = require('react-dom')
const App = require('./App')
ReactDOM.render(React.createElement(App), document.getElementById('root'))
App.js:
const React = require('react')
class App extends React.Component {
constructor (props) {
super(props)
this.state = {
color: false
}
}
render () {
const self = this
return React.createElement('div', {
onClick: function () {
self.setState({
color: !self.state.color
})
},
style: {
color: this.state.color ? 'red' : 'black'
}
}, 'hello react')
}
}
module.exports = App
接下來我們需要webpack打包index.js。
$ npm install webpack webpack-cli --save-dev
接着運作 webpack:
$ npx webpack --mode development (webpack 4新做的改進)
現在通路index.html,我們就能看到用戶端渲染出的内容了。
然後我們搭建一個簡單的node.js的伺服器,在 react-server-render 目錄下建立一個 index.js 檔案:
const http = require('http')
const server = http.createServer(function (request, response) {
response.writeHead(200, { 'Content-Type': 'text/html' })
response.write('hello react')
response.end()
})
server.listen(3000)
執行 node index.js,我們就能在 http://localhost:4200 通路頁面 - 看到寫死的 hello react.js。
那要如何傳回 html 檔案?
也很簡單,我們讀取 html 檔案并傳回即可:
const http = require('http')
const fs = require('fs')
const path = require('path')
const server = http.createServer(function (request, response) {
response.writeHead(200, { 'Content-Type': 'text/html' })
fs.createReadStream(path.join(__dirname, 'index.html')).pipe(response)
})
server.listen(3000)
但上面的代碼有一個問題,就是所有的請求都會傳回 index.html 的内容,不管它是 / 還是 dist/main.js。我們需要優化上面的代碼,區分不同的請求:
const http = require('http')
const fs = require('fs')
const path = require('path')
const server = http.createServer(function (request, response) {
if (request.url === '/') {
response.writeHead(200, { 'Content-Type': 'text/html' })
fs.createReadStream(path.join(__dirname, 'index.html')).pipe(response)
} else {
if (/\.js/.test(request.url)) {
response.writeHead(200, { 'Content-Type': 'application/javascript' })
fs.createReadStream(path.join(__dirname, `${request.url}`)).pipe(response)
} else {
response.writeHead(404)
response.end()
}
}
})
server.listen(3000)
好了,我們成功運作一個 Node.js 應用,它能夠傳回 html 檔案,也能傳回 JavaScript 檔案。當然,大部分時候我們不需要寫原生的 Node.js 代碼,而是使用 Express.js 等成熟架構。同理,我們可以傳回伺服器端渲染的 React 代碼, 我們要用到 ReactDOMServer 提供的 renderToString。
const http = require('http')
const fs = require('fs')
const path = require('path')
const ReactDOMServer = require('react-dom/server')
const React = require('react')
const App = require('./src/App.js')
function renderHTML (body) {
return `
<!DOCTYPE html>
<html >
<head>
<title>React 伺服器端渲染</title>
</head>
<body>
<div id="root">${body}</div>
<script src="dist/main.js"></script>
</body>
</html>
`
}
const server = http.createServer(function (request, response) {
if (request.url === '/') {
let body = ReactDOMServer.renderToString(React.createElement(App))
response.writeHead(200, { 'Content-Type': 'text/html' })
response.write(renderHTML(body))
response.end()
} else {
if (/\.js/.test(request.url)) {
response.writeHead(200, { 'Content-Type': 'application/javascript' })
fs.createReadStream(path.join(__dirname, `${request.url}`)).pipe(response)
} else {
response.writeHead(404)
response.end()
}
}
})
server.listen(3000)
如上,一個簡單的基于react 的伺服器端渲染已經完成。
通過控制台模拟Slow 3G的網絡環境,可以明顯發現,通過用戶端渲染的時候,會産生白屏現象,而通過伺服器端渲染,
無白屏現象。作為前端小白,在我了解的是,可以用戶端渲染為主,伺服器端渲染為輔,用伺服器端渲染進行頁面的首屏加載,這
樣,不僅會加快渲染速度,也不會造成伺服器端太大的壓力。
參考:
https://reactjs.org/docs/react-dom-server.html
人真是懶惰的生物。好久沒有認真的開始學習,生活好像沒有了目标。