天天看點

react服務端渲染(1)

使用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 更友好。

伺服器端渲染流程

傳統的伺服器端渲染大緻是一個這樣的流程:

  1. 從背景資料中讀取資訊
  2. 編譯模闆生成HTML代碼
  3. 傳回HTML給用戶端
  4. 浏覽器解析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

人真是懶惰的生物。好久沒有認真的開始學習,生活好像沒有了目标。