React學(xué)習(xí)之React服務(wù)端渲染

服務(wù)端渲染一個(gè)很常見(jiàn)的場(chǎng)景是當(dāng)用戶(或搜索引擎爬蟲(chóng))第一次請(qǐng)求頁(yè)面時(shí)费彼,用它來(lái)做初始渲染箍邮。當(dāng)服務(wù)器接收到請(qǐng)求后坝锰,它把需要的組件渲染成 HTML 字符串处窥,然后把它返回給客戶端(這里統(tǒng)指瀏覽器)雹仿。之后增热,客戶端會(huì)接手渲染控制權(quán)。

下面我們使用 React 來(lái)做示例胧辽,對(duì)于支持服務(wù)端渲染的其它 view 框架峻仇,做法也是類似的。

服務(wù)端使用 Redux

當(dāng)在服務(wù)器使用 Redux 渲染時(shí)邑商,一定要在響應(yīng)中包含應(yīng)用的 state摄咆,這樣客戶端可以把它作為初始 state。這點(diǎn)至關(guān)重要人断,因?yàn)槿绻谏?HTML 前預(yù)加載了數(shù)據(jù)豆同,我們希望客戶端也能訪問(wèn)這些數(shù)據(jù)。否則含鳞,客戶端生成的 HTML 與服務(wù)器端返回的 HTML 就會(huì)不匹配影锈,客戶端還需要重新加載數(shù)據(jù)。

把數(shù)據(jù)發(fā)送到客戶端蝉绷,需要以下步驟:

  • 為每次請(qǐng)求創(chuàng)建全新的 Redux store 實(shí)例鸭廷;
  • 按需 dispatch 一些 action;
  • 從 store 中取出 state熔吗;
  • 把 state 一同返回給客戶端辆床。
    在客戶端,使用服務(wù)器返回的 state 創(chuàng)建并初始化一個(gè)全新的 Redux store桅狠。
    Redux 在服務(wù)端惟一要做的事情就是讼载,提供應(yīng)用所需的初始 state轿秧。

安裝

下面來(lái)介紹如何配置服務(wù)端渲染。使用極簡(jiǎn)的 Counter 計(jì)數(shù)器應(yīng)用 來(lái)做示例咨堤,介紹如何根據(jù)請(qǐng)求在服務(wù)端提前渲染 state菇篡。

安裝依賴庫(kù)

本例會(huì)使用 Express 來(lái)做小型的 web 服務(wù)器。還需要安裝 Redux 對(duì) React 的綁定庫(kù)一喘,Redux 默認(rèn)并不包含驱还。

npm install --save express react-redux

服務(wù)端開(kāi)發(fā)

下面是服務(wù)端代碼大概的樣子。使用 app.use 掛載 Express middleware 處理所有請(qǐng)求凸克。不熟悉 Express 或者 middleware议蟆,只需要了解每次服務(wù)器收到請(qǐng)求時(shí)都會(huì)調(diào)用 handleRender 函數(shù)。

另外萎战,如果有使用 ES6 和 JSX 語(yǔ)法咐容,需要使用 Babel (對(duì)應(yīng)示例this example of a Node Server with Babel) 和 React preset。

server.js
import path from 'path'
import Express from 'express'
import React from 'react'
import { createStore } from 'redux'
import { Provider } from 'react-redux'
import counterApp from './reducers'
import App from './containers/App'

const app = Express()
const port = 3000

// 提供靜態(tài)文件
app.use('/static', Express.static('static'))

// 每當(dāng)收到請(qǐng)求時(shí)都會(huì)觸發(fā)
app.use(handleRender)

// 接下來(lái)會(huì)補(bǔ)充這部分代碼
function handleRender(req, res) {
  /* ... */
}
function renderFullPage(html, preloadedState) {
  /* ... */
}

app.listen(port)

處理請(qǐng)求

第一件要做的事情就是對(duì)每個(gè)請(qǐng)求創(chuàng)建一個(gè)新的 Redux store 實(shí)例蚂维。這個(gè) store 惟一作用是提供應(yīng)用初始的 state疟丙。

渲染時(shí),使用 <Provider> 來(lái)包住根組件 <App />鸟雏,以此來(lái)讓組件樹(shù)中所有組件都能訪問(wèn)到 store享郊,就像之前的搭配 React 教程講的那樣。

服務(wù)端渲染最關(guān)鍵的一步是在發(fā)送響應(yīng)前渲染初始的 HTML孝鹊。這就要使用 ReactDOMServer.renderToString()炊琉。

然后使用 store.getState() 從 store 得到初始 state。renderFullPage 函數(shù)會(huì)介紹接下來(lái)如何傳遞又活。

import { renderToString } from 'react-dom/server'

function handleRender(req, res) {
  // 創(chuàng)建新的 Redux store 實(shí)例
  const store = createStore(counterApp)

  // 把組件渲染成字符串
  const html = renderToString(
    <Provider store={store}>
      <App />
    </Provider>
  )

  // 從 store 中獲得初始 state
  const preloadedState = store.getState()

  // 把渲染后的頁(yè)面內(nèi)容發(fā)送給客戶端
  res.send(renderFullPage(html, preloadedState))
}

注入初始組件的 HTML 和 State

服務(wù)端最后一步就是把初始組件的 HTML 和初始 state 注入到客戶端能夠渲染的模板中苔咪。如何傳遞 state 呢,我們添加一個(gè) <script> 標(biāo)簽來(lái)把 preloadedState 賦給 window.PRELOADED_STATE柳骄。

客戶端可以通過(guò) window.PRELOADED_STATE 獲取 preloadedState团赏。

同時(shí)使用 script 標(biāo)簽來(lái)引入打包后的 js bundle 文件。這是打包工具輸出的客戶端入口文件耐薯,以靜態(tài)文件或者 URL 的方式實(shí)現(xiàn)服務(wù)端開(kāi)發(fā)中的熱加載舔清。下面是代碼。

function renderFullPage(html, preloadedState) {
  return `
    <!doctype html>
    <html>
      <head>
        <title>Redux Universal Example</title>
      </head>
      <body>
        <div id="root">${html}</div>
        <script>
          // 警告:關(guān)于在 HTML 中嵌入 JSON 的安全問(wèn)題曲初,請(qǐng)查看以下文檔
          // http://redux.js.org/recipes/ServerRendering.html#security-considerations
          window.__PRELOADED_STATE__ = ${JSON.stringify(preloadedState).replace(
            /</g,
            '\\u003c'
          )}
        </script>
        <script src="/static/bundle.js"></script>
      </body>
    </html>
    `
}

未完待續(xù)...

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末体谒,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子臼婆,更是在濱河造成了極大的恐慌抒痒,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,277評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件颁褂,死亡現(xiàn)場(chǎng)離奇詭異故响,居然都是意外死亡傀广,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門(mén)彩届,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)伪冰,“玉大人,你說(shuō)我怎么就攤上這事惨缆∶又担” “怎么了丰捷?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,624評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵坯墨,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我病往,道長(zhǎng)捣染,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,356評(píng)論 1 293
  • 正文 為了忘掉前任停巷,我火速辦了婚禮耍攘,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘畔勤。我一直安慰自己蕾各,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布庆揪。 她就那樣靜靜地躺著式曲,像睡著了一般。 火紅的嫁衣襯著肌膚如雪缸榛。 梳的紋絲不亂的頭發(fā)上吝羞,一...
    開(kāi)封第一講書(shū)人閱讀 51,292評(píng)論 1 301
  • 那天,我揣著相機(jī)與錄音内颗,去河邊找鬼钧排。 笑死,一個(gè)胖子當(dāng)著我的面吹牛均澳,可吹牛的內(nèi)容都是我干的恨溜。 我是一名探鬼主播,決...
    沈念sama閱讀 40,135評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼找前,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼筒捺!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起纸厉,我...
    開(kāi)封第一講書(shū)人閱讀 38,992評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤系吭,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后颗品,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體肯尺,經(jīng)...
    沈念sama閱讀 45,429評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡沃缘,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評(píng)論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了则吟。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片槐臀。...
    茶點(diǎn)故事閱讀 39,785評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖氓仲,靈堂內(nèi)的尸體忽然破棺而出水慨,到底是詐尸還是另有隱情,我是刑警寧澤敬扛,帶...
    沈念sama閱讀 35,492評(píng)論 5 345
  • 正文 年R本政府宣布晰洒,位于F島的核電站,受9級(jí)特大地震影響啥箭,放射性物質(zhì)發(fā)生泄漏谍珊。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評(píng)論 3 328
  • 文/蒙蒙 一急侥、第九天 我趴在偏房一處隱蔽的房頂上張望砌滞。 院中可真熱鬧,春花似錦坏怪、人聲如沸贝润。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,723評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)打掘。三九已至,卻和暖如春捉超,著一層夾襖步出監(jiān)牢的瞬間胧卤,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,858評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工拼岳, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留枝誊,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,891評(píng)論 2 370
  • 正文 我出身青樓惜纸,卻偏偏與公主長(zhǎng)得像叶撒,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子耐版,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容