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


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 發出異步請求,並渲染返回的 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上的差異

To get started, we'll install Express, a Node.js server side application framework:
開始前,讓我們安裝 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>`);

To start the server, update the start script in package.json and then run npm run start:
要啟動服務器,請更新 `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上查看完整的差異

To move data fetching before rendering, we'll install react-transmit:
要在渲染之前獲取數據,我們需安裝 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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM