[譯] React 在服務端渲染的實現

牧云云發表於2017-07-04

React 在服務端渲染的實現

React是最受歡迎的客戶端 JavaScript 框架,但你知道嗎(或許更應該試試),你可以使用 React 在伺服器端進行渲染?

假設你為客戶構建了一個很棒的事件列表 React app。。該應用程式使用了您最喜歡的伺服器端工具構建的API。幾周後,使用者告訴您,他們的頁面沒有顯示在 Google 上,釋出到 Facebook 時也顯示不出來。 這些問題似乎是可以解決的,對吧?

您會發現,要解決這個問題,需要在初始載入時從伺服器渲染 React 頁面,以便來自搜尋引擎和社交媒體網站的爬蟲工具可以讀取您的標記。有證據表明,Google 有時會執行 javascript 程式並且對生成的內容進行索引,但並不總是這樣。因此,如果您希望確保與其他服​​務(如 Facebook、Twitter)有良好的 SEO 相容性,那麼始終建議使用伺服器端渲染。

在本教程中,我們將逐步介紹伺服器端的呈現示例。包括圍繞與 API 交流的 React 應用程式的共同路障。
在本教程中,我們將逐步向您介紹伺服器端的渲染示例。包括圍繞著 APIS 交流一些在服務端渲染 React 應用程式的共同障礙。

服務端渲染的優勢

可能您的團隊談論到服務端渲染的好處是首先會想到 SEO,但這並不是唯一的潛在好處。

更大的好處如下:伺服器端渲染能更快地顯示頁面。使用伺服器端渲染,您的伺服器對瀏覽器進行響應是在您的 HTML 頁面可以渲染的時候,因此瀏覽器可以不用等待所有的 JavaScript 被下載和執行就可以開始渲染。當瀏覽器下載並執行頁面所需的 JavaScript 和其他資源時,不會出現 “白屏” 現象,而 “白屏” 卻可能在完全由客戶端渲染的 React 網站中出現。

入門

接下來讓我們來看看如何將伺服器端渲染新增到一個基本的客戶端渲染的使用 Babel 和 Webpack 的 React 應用程式中。我們的應用程式將會因從第三方 API 獲取資料而變得有點複雜。我們在 GitHub 上提供了相關程式碼,您可以在其中看到完整的示例。

提供的程式碼中只有一個 React 元件,`hello.js`,這個檔案將向 ButterCMS API 發出非同步請求,並渲染返回 JSON 列表中的博文。ButterCMS 是一個基於 API 的部落格引擎,可供個人使用,因此它非常適合測試現實生活中的用例。啟動程式碼中連線著一個 API token,如果你想使用你自己的 API token 可以使用你的 GitHub 賬號登入 ButterCMS

import React from 'react';
import Butter from 'buttercms'

const butter = Butter('b60a008584313ed21803780bc9208557b3b49fbb');

var Hello = React.createClass({
  getInitialState: function() {
    return {loaded: false};
  },
  componentWillMount: function() {
    butter.post.list().then((resp) => {
      this.setState({
        loaded: true,
        resp: resp.data
      })
    });
  },
  render: function() {
    if (this.state.loaded) {
      return (
        <div>
          {this.state.resp.data.map((post) => {
            return (
              <div key={post.slug}>{post.title}</div>
            )
          })}
        </div>
      );
    } else {
      return <div>Loading...</div>;
    }
  }
});

export default Hello;複製程式碼

啟動器程式碼中包含以下內容:

  • package.json - 依賴項
  • Webpack 和 Babel 配置
  • index.html - app 的 HTML 檔案
  • index.js - 載入 React 並渲染 Hello 元件

要使應用執行,請先克隆資源庫:

git clone ...
cd ..複製程式碼

安裝依賴:

npm install複製程式碼

然後啟動伺服器:

npm run start複製程式碼

瀏覽器輸入 http://localhost:8000 可以看到這個 app: (這裡譯者進行補充,package.json 裡的 start 命令改為如下:"start": webpack-dev-server --watch)

如果您檢視渲染頁面的原始碼,您將看到傳送到瀏覽器的標記只是一個到 JavaScript 檔案的連結。這意味著頁面的內容不能保證被搜尋引擎和社交媒體平臺抓取:

增加伺服器端渲染

接下來,我們將實現伺服器端渲染,以便將完全生成的 HTML 傳送到瀏覽器。如果要同時檢視所有更改,請在 GitHub 上檢視檔案的差異。

開始前,讓我們安裝 Express,一個 Node.js 的伺服器端應用程式框架:

npm install express --save複製程式碼

我們要建立一個渲染我們的 React 元件的伺服器:

import express from 'express';
import fs from 'fs';
import path from 'path';
import React from 'react';
import ReactDOMServer from 'react-dom/server';
import Hello from './Hello.js';

function handleRender(req, res) {
  // 把 Hello 元件渲染成 HTML 字串
  const html = ReactDOMServer.renderToString(<Hello />);

  // 載入 index.html 的內容
  fs.readFile('./index.html', 'utf8', function (err, data) {
    if (err) throw err;

    // 把渲染後的 React HTML 插入到 div 中
    const document = data.replace(/<div id="app"><\/div>/, `<div id="app">${html}</div>`);

    // 把響應傳回給客戶端
    res.send(document);
  });
}

const app = express();

// 伺服器使用 static 中介軟體構建 build 路徑
app.use('/build', express.static(path.join(__dirname, 'build')));

// 使用我們的 handleRender 中介軟體處理服務端請求
app.get('*', handleRender);

// 啟動伺服器
app.listen(3000);複製程式碼

讓我們分解下程式看看發生了什麼事情...

handleRender 函式處理所有請求。在檔案頂部匯入的 ReactDOMServer 類提供了將 React 節點渲染成其初始 HTML 的 renderToString() 方法

ReactDOMServer.renderToString(<Hello />);複製程式碼

這將返回 Hello 元件的 HTML,我們將其注入到 index.html 的 HTML 中,從而生成伺服器上頁面的完整 HTML。

const document = data.replace(/<div id="app"><\/div>/,`<div id="app">${html}</div>`);複製程式碼

要啟動伺服器,請更新 `package.json` 中的起始指令碼,然後執行 npm run start :

"scripts": {
  "start": "webpack && babel-node server.js"
},複製程式碼

瀏覽 http://localhost:3000 檢視應用程式。瞧!您的頁面現在正在從伺服器渲染出來了。但是有個問題,
如果您在瀏覽器中檢視頁面原始碼,您會注意到部落格文章仍未包含在響應中。這是怎麼回事?如果我們在 Chrome 中開啟網路皮膚,我們會看到客戶端上發生 API 請求。

雖然我們在伺服器上渲染了 React 元件,但是 API 請求在 componentWillMount 中非同步生成,並且元件在請求完成之前渲染。所以即使我們已經在伺服器上完成渲染,但我們只是完成了部分。事實上,React repo 有一個 issue,超過 100 條評論討論了這個問題和各種解決方法。

在渲染之前獲取資料

要解決這個問題,我們需要在渲染 Hello 元件之前確保 API 請求完成。這意味著要使 API 請求跳出 React 的元件渲染迴圈,並在渲染元件之前獲取資料。我們將逐步介紹這一步,但您可以在 GitHub 上檢視完整的差異

要在渲染之前獲取資料,我們需安裝 react-transmit

npm install react-transmit --save複製程式碼

React Transmit 給了我們優雅的包裝器元件(通常稱為“高階元件”),用於獲取在客戶端和伺服器上工作的資料。

這是我們使用 react-transmit 後的元件的程式碼:

import React from 'react';
import Butter from 'buttercms'
import Transmit from 'react-transmit';

const butter = Butter('b60a008584313ed21803780bc9208557b3b49fbb');

var Hello = React.createClass({
  render: function() {
    if (this.props.posts) {
      return (
        <div>
          {this.props.posts.data.map((post) => {
            return (
              <div key={post.slug}>{post.title}</div>
            )
          })}
        </div>
      );
    } else {
      return <div>Loading...</div>;
    }
  }
});

export default Transmit.createContainer(Hello, {
  // 必須設定 initiallVariables 和 ftagments ,否則渲染時會報錯
  initialVariables: {},
  // 定義的方法名將成為 Transmit props 的名稱
  fragments: {
    posts() {
      return butter.post.list().then((resp) => resp.data);
    }
  }
});複製程式碼

我們已經使用 Transmit.createContainer 將我們的元件包裝在一個高階元件中,該元件可以用來獲取資料。我們在 React 元件中刪除了生命週期方法,因為無需兩次獲取資料。同時我們把 render 方法中的 state 替換成 props,因為 React Transmit 將資料作為 props 傳遞給元件。

為了確保伺服器在渲染之前獲取資料,我們匯入 Transmit 並使用 Transmit.renderToString 而不是 ReactDOM.renderToString 方法

import express from 'express';
import fs from 'fs';
import path from 'path';
import React from 'react';
import ReactDOMServer from 'react-dom/server';
import Hello from './Hello.js';
import Transmit from 'react-transmit';

function handleRender(req, res) {
  Transmit.renderToString(Hello).then(({reactString, reactData}) => {
    fs.readFile('./index.html', 'utf8', function (err, data) {
      if (err) throw err;

      const document = data.replace(/<div id="app"><\/div>/, `<div id="app">${reactString}</div>`);
      const output = Transmit.injectIntoMarkup(document, reactData, ['/build/client.js']);

      res.send(document);
    });
  });
}

const app = express();

// 伺服器使用 static 中介軟體構建 build 路徑
app.use('/build', express.static(path.join(__dirname, 'build')));

// 使用我們的 handleRender 中介軟體處理服務端請求
app.get('*', handleRender);

// 啟動伺服器
app.listen(3000);複製程式碼

重新啟動伺服器瀏覽到 http://localhost:3000。檢視頁面原始碼,您將看到該頁面現在完全呈現在伺服器上!

更進一步

我們做到了!在伺服器上使用 React 可能很棘手,尤其是從 API 獲取資料時。幸運的是,React 社群正在蓬勃發展,並創造了許多有用的工具。如果您對構建在客戶端和伺服器上渲染的大型 React 應用程式的框架感興趣,請檢視 Walmart Labs 的 ElectrodeNext.js。或者如果要在 Ruby 中渲染 React ,請檢視 Airbnb 的 Hypernova


掘金翻譯計劃 是一個翻譯優質網際網路技術文章的社群,文章來源為 掘金 上的英文分享文章。內容覆蓋 AndroidiOSReact前端後端產品設計 等領域,想要檢視更多優質譯文請持續關注 掘金翻譯計劃

相關文章