詳解React 在服務(wù)端渲染的實(shí)現(xiàn)
React是最受歡迎的客戶端 JavaScript 框架,但你知道嗎(可以試試),你可以使用 React 在服務(wù)器端進(jìn)行渲染?
假設(shè)你已經(jīng)在客戶端使用 React 構(gòu)建了一個(gè)事件列表 app。該應(yīng)用程序使用了您最喜歡的服務(wù)器端工具構(gòu)建的API。幾周后,用戶告訴您,他們的頁(yè)面沒有顯示在 Google 上,發(fā)布到 Facebook 時(shí)也顯示不出來(lái)。 這些問題似乎是可以解決的,對(duì)吧?
您會(huì)發(fā)現(xiàn),要解決這個(gè)問題,需要在初始加載時(shí)從服務(wù)器渲染 React 頁(yè)面,以便來(lái)自搜索引擎和社交媒體網(wǎng)站的爬蟲工具可以讀取您的標(biāo)記。有證據(jù)表明,Google 有時(shí)會(huì)執(zhí)行 javascript 程序并且對(duì)生成的內(nèi)容進(jìn)行索引,但并不總是的。因此,如果您希望確保與其他服務(wù)(如Facebook,Twitter)有良好的SEO兼容性,那么始終建議使用服務(wù)器端渲染。
在本教程中,我們將逐步介紹服務(wù)器端的呈現(xiàn)示例。包括圍繞與API交流的React應(yīng)用程序的共同路障。
在本教程中,我們將逐步向您介紹服務(wù)器端的渲染示例。包括圍繞著 APIS 交流一些在服務(wù)端渲染 React 應(yīng)用程序的共同障礙。
服務(wù)端渲染的優(yōu)勢(shì)
可能您的團(tuán)隊(duì)談?wù)摰椒?wù)端渲染的好處是首先會(huì)想到 SEO,但這并不是唯一的潛在好處。
更大的好處如下:服務(wù)器端渲染能更快地顯示頁(yè)面。使用服務(wù)器端渲染,您的服務(wù)器對(duì)瀏覽器進(jìn)行響應(yīng)是在您的 HTML 頁(yè)面可以渲染的時(shí)候,因此瀏覽器可以不用等待所有的 JavaScript 被下載和執(zhí)行就可以開始渲染。當(dāng)瀏覽器下載并執(zhí)行頁(yè)面所需的 JavaScript 和其他資源時(shí),不會(huì)出現(xiàn) “白屏” 現(xiàn)象,而 “白屏” 這是在完全有客戶端呈現(xiàn)的 React 網(wǎng)站中可能發(fā)生的情況。
入門
接下來(lái)讓我們來(lái)看看如何將服務(wù)器端渲染添加到一個(gè)基本的客戶端渲染的使用Babel和Webpack的React應(yīng)用程序中。我們的應(yīng)用程序?qū)⒃黾訌牡谌?API 獲取數(shù)據(jù)的復(fù)雜性。我們?cè)贕itHub上提供了相關(guān)代碼,您可以在其中看到完整的示例。
提供的代碼中只有一個(gè) React 組件,`hello.js`,這個(gè)文件將向 ButterCMS 發(fā)出異步請(qǐng)求,并渲染返回的 JSON 列表的博文。ButterCMS 是一個(gè)基于API的博客引擎,可供個(gè)人使用,因此它非常適合測(cè)試現(xiàn)實(shí)生活中的用例。啟動(dòng)代碼中連接著一個(gè) API token,如果你想使用你自己的 API token 可以使用你的 GitHub 賬號(hào)登入 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;
啟動(dòng)器代碼中包含以下內(nèi)容:
- package.json - 依賴項(xiàng)
- Webpack 和 Babel 配置
- index.html - app 的 HTML 文件
- index.js - 加載 React 并渲染 Hello 組件
要使應(yīng)用運(yùn)行,請(qǐng)先克隆資源庫(kù):
git clone ... cd ..
安裝依賴:
npm install
然后啟動(dòng)服務(wù)器:
npm run start
瀏覽器輸入 http://localhost:8000 可以看到這個(gè) app: (這里譯者進(jìn)行補(bǔ)充,package.json 里的 start 命令改為如下:"start": webpack-dev-server --watch)
如果您查看渲染頁(yè)面的源代碼,您將看到發(fā)送到瀏覽器的標(biāo)記只是一個(gè)到 JavaScript 文件的鏈接。這意味著頁(yè)面的內(nèi)容不能保證被搜索引擎和社交媒體平臺(tái)抓取:
增加服務(wù)器端渲染
接下來(lái),我們將實(shí)現(xiàn)服務(wù)器端渲染,以便將完全生成的HTML發(fā)送到瀏覽器。如果要同時(shí)查看所有更改,請(qǐng)查看GitHub上的差異。
To get started, we'll install Express, a Node.js server side application framework:
開始前,讓我們安裝 Express,一個(gè) Node.js 的服務(wù)器端應(yīng)用程序框架:
npm install express --save
我們要?jiǎng)?chuàng)建一個(gè)渲染我們的 React 組件的服務(wù)器:
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 的內(nèi)容 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>`); // 把響應(yīng)傳回給客戶端 res.send(document); }); } const app = express(); // 服務(wù)器使用 static 中間件構(gòu)建 build 路徑 app.use('/build', express.static(path.join(__dirname, 'build'))); // 使用我們的 handleRender 中間件處理服務(wù)端請(qǐng)求 app.get('*', handleRender); // 啟動(dòng)服務(wù)器 app.listen(3000);
讓我們分解下程序看看發(fā)生了什么事情...
handleRender 函數(shù)處理所有請(qǐng)求。在文件頂部導(dǎo)入的ReactDOMServer 類提供了將 React 節(jié)點(diǎn)渲染成其初始 HTML 的 renderToString() 方法
ReactDOMServer.renderToString(<Hello />);
這將返回 Hello 組件的 HTML ,我們將其注入到 index.html 的 HTML 中,從而生成服務(wù)器上頁(yè)面的完整 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:
要啟動(dòng)服務(wù)器,請(qǐng)更新 `package.json` 中的起始腳本,然后運(yùn)行 npm run start :
"scripts": { "start": "webpack && babel-node server.js" },
瀏覽 http://localhost:3000 查看應(yīng)用程序。瞧!您的頁(yè)面現(xiàn)在正在從服務(wù)器渲染出來(lái)了。但是有個(gè)問題,如果您在瀏覽器中查看頁(yè)面源碼,您會(huì)注意到博客文章仍未包含在回復(fù)中。這是怎么回事?如果我們?cè)贑hrome中打開網(wǎng)絡(luò)標(biāo)簽,我們會(huì)看到客戶端上發(fā)生API請(qǐng)求。
雖然我們?cè)?服務(wù)器上渲染了 React 組件,但是 API 請(qǐng)求在 componentWillMount 中異步生成,并且組件在請(qǐng)求完成之前渲染。所以即使我們已經(jīng)在服務(wù)器上完成渲染,但我們只是完成了部分。事實(shí)上,React repo 有一個(gè) issue,超過(guò) 100 條評(píng)論討論了這個(gè)問題和各種解決方法。
在渲染之前獲取數(shù)據(jù)
要解決這個(gè)問題,我們需要在渲染 Hello 組件之前確保 API 請(qǐng)求完成。這意味著要使 API 請(qǐng)求跳出 React 的組件渲染循環(huán),并在渲染組件之前獲取數(shù)據(jù)。我們將逐步介紹這一步,但您可以在GitHub上查看完整的差異。
To move data fetching before rendering, we'll install react-transmit:
要在渲染之前獲取數(shù)據(jù),我們需安裝 react-transmit:
npm install react-transmit --save
React Transmit 給了我們優(yōu)雅的包裝器組件(通常稱為“高階組件”),用于獲取在客戶端和服務(wù)器上工作的數(shù)據(jù)。
這是我們使用 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, { // 必須設(shè)定 initiallVariables 和 ftagments ,否則渲染時(shí)會(huì)報(bào)錯(cuò) initialVariables: {}, // 定義的方法名將成為 Transmit props 的名稱 fragments: { posts() { return butter.post.list().then((resp) => resp.data); } } });
我們已經(jīng)使用 Transmit.createContainer 將我們的組件包裝在一個(gè)高級(jí)組件中,該組件可以用來(lái)獲取數(shù)據(jù)。我們?cè)?React 組件中刪除了生命周期方法,因?yàn)闊o(wú)需兩次獲取數(shù)據(jù)。同時(shí)我們把 render 方法中的 state 替換成 props,因?yàn)?React Transmit 將數(shù)據(jù)作為 props 傳遞給組件。
為了確保服務(wù)器在渲染之前獲取數(shù)據(jù),我們導(dǎo)入 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(); // 服務(wù)器使用 static 中間件構(gòu)建 build 路徑 app.use('/build', express.static(path.join(__dirname, 'build'))); // 使用我們的 handleRender 中間件處理服務(wù)端請(qǐng)求 app.get('*', handleRender); // 啟動(dòng)服務(wù)器 app.listen(3000);
重新啟動(dòng)服務(wù)器瀏覽到 http://localhost:3000。查看頁(yè)面源代碼,您將看到該頁(yè)面現(xiàn)在完全呈現(xiàn)在服務(wù)器上!
更進(jìn)一步
我們做到了!在服務(wù)器上使用 React 可能很棘手,尤其是從 API 獲取數(shù)據(jù)時(shí)。幸運(yùn)的是,React社區(qū)正在蓬勃發(fā)展,并創(chuàng)造了許多有用的工具。如果您對(duì)構(gòu)建在客戶端和服務(wù)器上渲染的大型 React 應(yīng)用程序的框架感興趣,請(qǐng)查看 Walmart Labs 的 Electrode 或Next.js。或者如果要在 Ruby 中渲染 React ,請(qǐng)查看 AirBnB 的 Hypernova 。
原文地址:https://css-tricks.com/server-side-react-rendering/
原文作者:Roger Jin
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
ReactNative踩坑之配置調(diào)試端口的解決方法
本篇文章主要介紹了ReactNative踩坑之配置調(diào)試端口的解決方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-07-07react自適應(yīng)布局px轉(zhuǎn)rem實(shí)現(xiàn)示例詳解
這篇文章主要為大家介紹了react自適應(yīng)布局px轉(zhuǎn)rem實(shí)現(xiàn)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-08-08React中g(shù)etDefaultProps的使用小結(jié)
React中的getDefaultProps功能允許開發(fā)者為類組件定義默認(rèn)屬性,提高組件的靈活性和容錯(cuò)性,本文介紹了getDefaultProps的作用、語(yǔ)法以及最佳實(shí)踐,并探討了其他替代方案,如函數(shù)組件中的默認(rèn)參數(shù)、高階組件和ContextAPI等,理解這些概念有助于提升代碼的可維護(hù)性和用戶體驗(yàn)2024-09-09React-Native TextInput組件詳解及實(shí)例代碼
這篇文章主要介紹了React-Native TextInput組件詳解及實(shí)例代碼的相關(guān)資料,需要的朋友可以參考下2016-10-10React-router4路由監(jiān)聽的實(shí)現(xiàn)
這篇文章主要介紹了React-router4路由監(jiān)聽的實(shí)現(xiàn),小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-08-08React特征Form?單向數(shù)據(jù)流示例詳解
這篇文章主要為大家介紹了React特征Form?單向數(shù)據(jù)流示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-09-09React使用TailwindCSS的實(shí)現(xiàn)示例
TailwindCSS是一個(gè)實(shí)用優(yōu)先的CSS框架,本文主要介紹了React使用TailwindCSS的實(shí)現(xiàn)示例,具有一定的參考價(jià)值,感興趣的可以了解一下2023-12-12