本文的demo倉庫在 https://github.com/qiqingjin/blog/tree/master/React_Redux/,喜歡請star喲~
前面的話
React 16開始重視服務器端渲染,也就是SSR,不再嚴格對比checksum,不知道你的應用升級了麼。
Webpack 4的支持主要看依賴的plugin和loader,目前主流plugin已經支持,可以升級咯。
demo地址:https://github.com/qiqingjin/blog/tree/master/React_Redux/demos,使用了React,Redux和Webpack,服務器是Koa,你可以clone這個倉庫,使用 https://github.com/qiqingjin/blog/blob/master/React_Redux/README.md 這個步驟進行本地啓動。
基本思想
簡單來說,我要做的事情就是,寫一套react頁面,服務器端和瀏覽器端都可以把它們轉換成html頁面。當用戶請求某些頁面時,例如:首頁,先進行服務器端渲染,然後返回html頁面。瀏覽器會把服務器端html與瀏覽器端渲染的html頁面對比,並儘量複用服務器端返回的html,展示到瀏覽器上。注意,React 16不再使用checksum進行嚴格校驗。
面臨的問題
- 服務器端渲染不會執行ajax請求,如何進行數據請求
- 不同的路由,在瀏覽器端會請求不同的數據,如何在服務器端根據路由來進行數據處理
數據請求
react-dom/server
提供了一個renderToString
方法,可以把react的虛擬dom,也就是react component的類,轉換成html。我們只要在調用這個方法之前,提前執行需要在瀏覽器端執行的請求就好,看代碼:
// demos/server/server.js
const {data: todosData} = await axios.get('http://localhost:667/rest/todos');
然後需要獲取react組件,我這裏使用koa
,node
端不支持import
等方法,需要用webpack
把react組件編譯,注意,服務器端編譯的config配置和瀏覽器端編譯的config略有不同,請看demos/client/webpack.config.js
和demos/client/webpack.config.ssr.js
。獲取react組件的代碼:
// demos/server/server.js
const indexBundle = require('./dist/main.ssr');
const indexApp = indexBundle.__esModule ? indexBundle.default : indexBundle;
獲取react組件後,需要把數據注入到redux的createStore
方法中,再傳給react組件,保證react組件中的store
有dispatch
等方法。
// demos/server/server.js
const store = indexApp.createStore(initData);
const instance = indexApp.createApp(store);
const todosStr = ReactDOMServer.renderToString(instance);
最後,需要把數據放到window
對象上,讓瀏覽器端用來渲染瀏覽器端html,然後與服務器端html對比,儘量複用。
// demos/server/server.js
const syncScript = `<script id="server-data">window._SERVER_DATA=${JSON.stringify(initData)}</script>`;
const $ = await loadHTMLTemplate(path.resolve(__dirname, '../client/dist/index.html'));
$('#app').html(todosStr);
$('head').append(syncScript);
路由處理
這個地方目前我看到的處理方法有:
1. 使用react-router
的match
方法,這個方案比較常見
2. 把頁面分成多個thunk
,並綁定到客戶的的路由文件中,在renderToString
之前dispatch
這些異步方法,從而將數據更新到redux的store中,請參考
這裏我並沒有寫在我的代碼中,如果你想嘗試,可以clone
我的倉庫,自己進行優化。