- 原文地址:Server-Side React Rendering
- 原文做者:Roger Jin
- 譯文出自:掘金翻譯計劃
- 本文永久連接:github.com/xitu/gold-m…
- 譯者:牧云云
- 校對者:CACppuccino、xx1124961758
React是最受歡迎的客戶端 JavaScript 框架,但你知道嗎(或許更應該試試),你可使用 React 在服務器端進行渲染?javascript
假設你爲客戶構建了一個很棒的事件列表 React app。。該應用程序使用了您最喜歡的服務器端工具構建的API。幾周後,用戶告訴您,他們的頁面沒有顯示在 Google 上,發佈到 Facebook 時也顯示不出來。 這些問題彷佛是能夠解決的,對吧?css
您會發現,要解決這個問題,須要在初始加載時從服務器渲染 React 頁面,以便來自搜索引擎和社交媒體網站的爬蟲工具能夠讀取您的標記。有證據代表,Google 有時會執行 javascript 程序而且對生成的內容進行索引,但並不老是這樣。所以,若是您但願確保與其餘服務(如 Facebook、Twitter)有良好的 SEO 兼容性,那麼始終建議使用服務器端渲染。html
在本教程中,咱們將逐步介紹服務器端的呈現示例。包括圍繞與 API 交流的 React 應用程序的共同路障。
在本教程中,咱們將逐步向您介紹服務器端的渲染示例。包括圍繞着 APIS 交流一些在服務端渲染 React 應用程序的共同障礙。前端
可能您的團隊談論到服務端渲染的好處是首先會想到 SEO,但這並非惟一的潛在好處。java
更大的好處以下:服務器端渲染能更快地顯示頁面。使用服務器端渲染,您的服務器對瀏覽器進行響應是在您的 HTML 頁面能夠渲染的時候,所以瀏覽器能夠不用等待全部的 JavaScript 被下載和執行就能夠開始渲染。當瀏覽器下載並執行頁面所需的 JavaScript 和其餘資源時,不會出現 「白屏」 現象,而 「白屏」 卻可能在徹底由客戶端渲染的 React 網站中出現。node
接下來讓咱們來看看如何將服務器端渲染添加到一個基本的客戶端渲染的使用 Babel 和 Webpack 的 React 應用程序中。咱們的應用程序將會因從第三方 API 獲取數據而變得有點複雜。咱們在 GitHub 上提供了相關代碼,您能夠在其中看到完整的示例。react
提供的代碼中只有一個 React 組件,`hello.js`,這個文件將向 ButterCMS API 發出異步請求,並渲染返回 JSON 列表中的博文。ButterCMS 是一個基於 API 的博客引擎,可供我的使用,所以它很是適合測試現實生活中的用例。啓動代碼中鏈接着一個 API token,若是你想使用你本身的 API token 能夠使用你的 GitHub 帳號登入 ButterCMS。android
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;複製代碼
啓動器代碼中包含如下內容:webpack
要使應用運行,請先克隆資源庫:ios
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 的 Electrode 或 Next.js。或者若是要在 Ruby 中渲染 React ,請查看 Airbnb 的 Hypernova 。
掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 Android、iOS、React、前端、後端、產品、設計 等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃。