標籤:
教程目錄
react+redux教程(一)connect、applyMiddleware、thunk、webpackHotMiddleware
react+redux教程(二)redux的單一狀態樹完全替代了react的狀態機器?
react+redux教程(三)reduce()、filter()、map()、some()、every()、...展開屬性
react+redux教程(四)undo、devtools、router
react+redux教程(五)非同步、單一state樹結構、componentWillReceiveProps
react+redux教程(六)redux服務端渲染流程
連載中……
今天,我們要講解的是react+redux服務端渲染。個人認為,react擊敗angular的真正“殺手鐧”就是服務端渲染。我們為什麼要實現服務端渲染,主要是為了SEO。
例子
例子仍然是官方的計數器例子,不過我們實現了服務端渲染和state預先載入。
原始碼:
https://github.com/lewis617/myReact/tree/master/redux-examples/universal
虛擬API
首先,我們要類比一個api,用於非同步請求資料。代碼如下:
common/api/counter.js
function getRandomInt(min, max) { return Math.floor(Math.random() * (max - min)) + min}export function fetchCounter(callback) { // Rather than immediately returning, we delay our code with a timeout to simulate asynchronous behavior setTimeout(() => { callback(getRandomInt(1, 100)) }, 500) // In the case of a real world API call, you‘ll normally run into a Promise like this: // API.getUser().then(user => callback(user))}
服務端請求api,發送html串和state
server/server.js(部分代碼)
// This is fired every time the server side receives a requestapp.use(handleRender)function handleRender(req, res) { // Query our mock API asynchronously fetchCounter(apiResult => { // Read the counter from the request, if provided const params = qs.parse(req.query) const counter = parseInt(params.counter, 10) || apiResult || 0 // Compile an initial state const initialState = { counter } // Create a new Redux store instance const store = configureStore(initialState) // Render the component to a string const html = renderToString( <Provider store={store}> <App /> </Provider> ) // Grab the initial state from our Redux store const finalState = store.getState() // Send the rendered page back to the client res.send(renderFullPage(html, finalState)) })}function renderFullPage(html, initialState) { return ` <!doctype html> <html> <head> <title>Redux Universal Example</title> </head> <body> <div id="app">${html}</div> <script> window.__INITIAL_STATE__ = ${JSON.stringify(initialState)} </script> <script src="/static/bundle.js"></script> </body> </html> `}
- api寫好了,我們調用了這個api,即fetchCounter,這個api函數也會產生一個回調,我們在回調中擷取counter值
- 如果中介軟體請求中有參數,則const params = qs.parse(req.query),counter為parseInt(params.counter, 10)。否則counter為api的回調中返回的值apiResult,如果前兩個都沒有則為0。qs用於解析http請求中的querystring,就是?param=sth。
- 得到counter,我們就得到了state,用state作為參數,重建一個store執行個體,每次都要重建一個新的store執行個體。然後用react的服務端渲染產生一個html串,把它發送出去
- 同時,我們還要發送一個const finalState = store.getState()出去,讓用戶端拿到這個state渲染,為什嗎?因為要保證用戶端和服務端渲染的組件一樣。
“提了褲子不認人”的服務端渲染
既然有了服務端渲染,為何還要用用戶端渲染,因為服務端渲染是“提了褲子不認人”,渲染完,我們的程式就不會動了(因為是一堆字串),用戶端則可以讓程式繼續動起來,因為用戶端有js,可以調用方法重繪瀏覽器介面。
用戶端拿到state再渲染一次
既然要再渲染一次,為何還要服務端渲染?為了seo,我們的服務端渲染不只是給使用者看的,主要是給那些“低能”的網路爬蟲看的。
好吧,忍忍火,我們繼續工作,用戶端再渲染一次。
const initialState = window.__INITIAL_STATE__const store = configureStore(initialState)const rootElement = document.getElementById(‘app‘)render( <Provider store={store}> <App/> </Provider>, rootElement)
其實用戶端渲染也就拿到個初始state,然後render而已,沒有很多代碼。
我們的state是從window.__INITIAL_STATE__擷取的,因為服務端把要給用戶端的state放在了這個全域變數上面。
"玄乎"的預載state
預載state,說得這麼“玄乎”,好像很高大上,其實就是把state在伺服器那邊就產生好,然後傳過來直接給用戶端用。沒有那麼“玄乎”。
react+redux教程(六)redux服務端渲染流程