前一篇文章講到了為了預(yù)取數(shù)據(jù)路捧,各個(gè)組件的寫(xiě)法。這里從整體上講一個(gè)client和server分別應(yīng)該怎么做。
Server Side Rendering的一個(gè)明確目標(biāo)其實(shí)就是等“異步”操作都結(jié)束了,再renderToString然后返回給客戶(hù)端勾怒。這樣,客戶(hù)端沒(méi)有javascript的情況下声旺,依然可以看到數(shù)據(jù)(所以對(duì)爬蟲(chóng)是友好的)。
我用到的庫(kù)是 react-redux, react-router, redux-saga段只,所以是要讓redux-saga能夠處理完必要的請(qǐng)求之后腮猖,進(jìn)行第二次渲染,然后返回給客戶(hù)端赞枕。(用redux-thunk是一樣的道理澈缺, 需要等promise結(jié)束之后坪创,再調(diào)用renderToString,然后返回給客戶(hù)端)
廢話(huà)不多說(shuō)姐赡,下面是樣例代碼:
// express 處理請(qǐng)求入口
app.get('*', (req, res) => {
handleRender(req, res)
})
// 根據(jù)你自己的需求創(chuàng)建store莱预, 主要參考redux就行了
function createStoreForServer() {
const sagaMiddleware = createSagaMiddleware()
middlewares = [sagaMiddleware] // 根據(jù)需求自己可以加入其它中間件
let preloadedState = {} // 客戶(hù)端需要在這個(gè)地方加載服務(wù)器端傳過(guò)來(lái)的初始狀態(tài), 詳見(jiàn)redux文檔(http://redux.js.org/docs/recipes/ServerRendering.html)
let store = createStore(rootReducer, {}, applyMiddleware(...middlewares))
// 下面是關(guān)鍵點(diǎn)项滑, 這些方法是server端需要用到的
store.runSaga = () => sagaMiddleware.run(rootSaga)
store.close = () => store.dispatch(END)
return store
}
function handleRender(req, res) {
let store = createStoreForServer()
// 判斷saga的調(diào)用都結(jié)束了依沮, 然后開(kāi)始第二次渲染
store.runSaga().done.then(() => {
const html = renderToString(<Routes store={store} />)
res.send(renderFullPage(html, store.getState())) // renderFullPage 參見(jiàn)redux文件就行了
}
// 觸發(fā)第一次渲染, 可是返回值我們并不關(guān)心枪狂, 只要改變store即可
renderToString(<Routes store={store} />)
// 關(guān)停saga危喉, 第二次渲染的時(shí)候,忽略各種請(qǐng)求就好啦
store.close()
}
上面這些一做州疾,基本上就搞定啦辜限。服務(wù)器端渲染,只需引入了這么一小段代碼严蓖,就可以解決核心問(wèn)題了薄嫡。
我這里沒(méi)有提到的問(wèn)題還有(每個(gè)小點(diǎn)感覺(jué)都可以專(zhuān)門(mén)寫(xiě)一篇博客了):
- 取用戶(hù)私有數(shù)據(jù)怎么辦? 靠cookie颗胡。如何做呢毫深?我自己的實(shí)現(xiàn)并不完美,是把一些信息暫時(shí)放在store中了杭措,但是
react-cookie
可能有更好的解決辦法(我一時(shí)半會(huì)沒(méi)搞清楚怎么跟redux結(jié)合费什,就沒(méi)啟用)。 - 官方文檔中手素,renderFullPage中需要把html的結(jié)構(gòu)直接寫(xiě)在函數(shù)里面鸳址,可是html的內(nèi)容可能是動(dòng)態(tài)生成的,怎么辦泉懦?首先稿黍,為啥會(huì)動(dòng)態(tài)生成呢, 因?yàn)樯a(chǎn)環(huán)境打包的時(shí)候崩哩,js, css是需要帶hash的弃衍,因此html引用的js昼接, css的名字會(huì)變化。動(dòng)態(tài)生成我用了
HtmlWebpackPlugin
。其次剧董, 有個(gè)html文件作為模板了,怎么用renderFullPage
? 我用的是個(gè)超簡(jiǎn)單又笨的方法:讀取html文件缘回,然后字符串替換猛们。 - react-router在server,client需要用到不用的類(lèi)型的history棚贾,怎么處理窖维?這個(gè)在react-router的github上搜一搜就解決方案榆综。 我用的是
BrowserRouter
和StaticRouter
,注意StaticRouter
可能有重定向信息就是了铸史。
最后的最后鼻疮, 有人給我推薦過(guò)next.js
,據(jù)說(shuō)可以方便解決SSR問(wèn)題琳轿,我大概看了一下判沟, 跟原生的寫(xiě)法還是有一些不同的,如果新項(xiàng)目利赋,可以考慮在開(kāi)始的時(shí)候就啟用水评。這次我踩的坑已經(jīng)夠多,就沒(méi)有去用next.js
了(也是有點(diǎn)小擔(dān)心react 16.0
可能跟next.js
會(huì)不兼容媚送,導(dǎo)致我到時(shí)候不能順暢升級(jí))中燥。