React服務(wù)端渲染(總結(jié))
一、前言
為什么需要服務(wù)端渲染?什么情況下進(jìn)行服務(wù)端渲染?筆者認(rèn)為,當(dāng)我們要求渲染時間盡量快、頁面響應(yīng)速度快時(優(yōu)點(diǎn)),才會采用服務(wù)器渲染,并且應(yīng)該“按需”對頁面進(jìn)行渲染 ——“首次加載/首屏”。即服務(wù)端渲染的優(yōu)勢在于:由中間層( node端 )為客戶端請求初始數(shù)據(jù)、并由node渲染頁面。那客戶端渲染和服務(wù)端渲染有什么差別?服務(wù)端渲染究竟快在哪里呢?
二、原因與思路
客戶端渲染路線:1. 請求一個html -> 2. 服務(wù)端返回一個html -> 3. 瀏覽器下載html里面的js/css文件 -> 4. 等待js文件下載完成 -> 5. 等待js加載并初始化完成 -> 6. js代碼終于可以運(yùn)行,由js代碼向后端請求數(shù)據(jù)( ajax/fetch ) -> 7. 等待后端數(shù)據(jù)返回 -> 8. react-dom( 客戶端 )從無到完整地,把數(shù)據(jù)渲染為響應(yīng)頁面
服務(wù)端渲染路線:2. 請求一個html -> 2. 服務(wù)端請求數(shù)據(jù)( 內(nèi)網(wǎng)請求快 ) -> 3. 服務(wù)器初始渲染(服務(wù)端性能好,較快) -> 4. 服務(wù)端返回已經(jīng)有正確內(nèi)容的頁面 -> 5. 客戶端請求js/css文件 -> 6. 等待js文件下載完成 -> 7. 等待js加載并初始化完成 -> 8. react-dom( 客戶端 )把剩下一部分渲染完成( 內(nèi)容小,渲染快 )
說明:對同一個組件,服務(wù)端渲染“可視的”一部分( render/componentWillMount部分代碼 ),為確保組件有完善的生命周期及事件處理,客戶端需要再次渲染。即:服務(wù)端渲染,實(shí)際上也是需要客戶端進(jìn)行 再次地、但開銷很小的二次渲染。
時間耗時比較:
1. 數(shù)據(jù)請求:由服務(wù)端請求數(shù)據(jù)而不是客戶端請求數(shù)據(jù),這是“快”的一個主要原因。服務(wù)端在內(nèi)網(wǎng)進(jìn)行請求,數(shù)據(jù)響應(yīng)速度快??蛻舳嗽诓煌W(wǎng)絡(luò)環(huán)境進(jìn)行數(shù)據(jù)請求,且外網(wǎng)http請求開銷大,導(dǎo)致時間差(主要原因)。
2. 步驟:服務(wù)端是先請求數(shù)據(jù)然后渲染“可視”部分,而客戶端是等待js代碼下載、加載完成再請求數(shù)據(jù)、渲染。即:服務(wù)端渲染不用等待js代碼下載完成再請求數(shù)據(jù),并會返回一個已經(jīng)有內(nèi)容的頁面。
3. 渲染性能:服務(wù)端性能比客戶端高,渲染速度快( 猜測,該項(xiàng)數(shù)據(jù)不詳 )。
4. 渲染內(nèi)容:服務(wù)端渲染會把”可視“部分先渲染,然后交給客戶端再作部分渲染。而客戶端渲染,則是從無到有,需要經(jīng)歷完整的渲染步驟?! ?/p>
三、注意事項(xiàng)與問題
0. 項(xiàng)目依賴什么?答:node端:express、react-dom/server、webpack。前端:React、mobx(一個更好的redux)、React-router、webpack
1. 前端/node端共用那部分代碼?答:node端/前端有各自的入口文件,server.js/client.js,通過react-router的路由配置文件routes.js作中間層
// routes.js module.exports = ( <Route path="/" component={ IComponent } > <Route path="/todo" component={ AComponent }> </Route> </Route> )
2. 代碼是由前后端共享,那如何分平臺地操作不同代碼?答:通過webpack。對共享代碼,進(jìn)行不同平臺的,webpack(babel)編譯,通過在webpack.config.js中加入
// webpack.client.config.js plugins: [ new webpack.DefinePlugin({ '__isServer__': false, '__isClient__': true }) ] // webpack.server.config.js plugins: [ new webpack.DefinePlugin({ '__isServer__': true, '__isClient__': false }) ] // xxx.js if( __isServer__ ) { ... }else { ... }
4. 組件的生命周期是如何的呢?答:componentWillMount( node端 ) -> render( node端 ) -> 客戶端生命周期和以前一樣
5. 拉取數(shù)據(jù)后如何處理呢?答:先在node端根據(jù)數(shù)據(jù)渲染好,再把數(shù)據(jù)隨頁面返回至前端,再由React根據(jù)數(shù)據(jù)進(jìn)行渲染校對( 若前后端渲染結(jié)果不一致將報錯 )。應(yīng)該在componentWillMount讓組件進(jìn)行本地的數(shù)據(jù)同步
// 組件.js componentWillMount() { if( __isClient__ ) { this.todoStore.todos = window.initTodos; } } // node端返回 ` <!doctype html> <html lang="utf-8"> <head> <script> window.initTodo = ${...}</script> </head> <body> ... </body> <script src="/static/vendor.js"></script> <script src="/static/client.bundle.js"></script>
6. 前端/node端“入口文件”通過webpack構(gòu)建有什么不同?答:前端是為了解析JSX與es6代碼(包括mobx的es6 decorator),node端除了以上,還需要加入babel-plugin-transform-runtime,是為了在node良好地運(yùn)行es7 async / awatit
7. 如何保證node端能夠先請求數(shù)據(jù)然后再渲染?答:es7的async / await語法
8. 前端的react-router路由與node端路由如何配合?node如何知道該路由是渲染哪個數(shù)據(jù)呢?答:前端是以前的react-router配置,node端是react-router的match/RouterContext// 共享文件routes.js
const routes = ( <Route path="/" component={ IComponent } > <Route path="/todo" component={ AComponent }> </Route> </Route> ) // 前端入口文件client.js render( <Router routes={ routes } history={ browserHistory } />, ele ) // node端入口文件server.js let app = express(); app.get('/todo', (req, res) => { match({ routes: routes, location: req.url }, async (err, redirect, props) => { // match會幫我們找到要渲染的組件鏈,注:上面一行使用了async語法,因此可以在render之前使用await運(yùn)行拉取數(shù)據(jù)的代碼 let html = renderToString(<RouterContext {...props} />) res.send( indexPage(html) ) } }) // node端返回 let indexPage = (html)=>{ return ` <!doctype html> <html lang="utf-8"> <head> <script> </script> </head> <body> <section id="hzpapp" >${html}</section> </body> <script src="/static/vendor.js"></script> <script src="/static/client.bundle.js"></script> </html> }
9. client.js中是否還能繼續(xù)使用webpack的require.ensure ? 答:可以。但閃白明顯,且node端返回html后會有報錯,在加載腳本后該錯誤能忽略?!?/p>
10. 若我使用的是mobx,該如何實(shí)例化store ? 答:每一個node請求,都應(yīng)該返回一個新的獨(dú)立的store實(shí)例,而不是每個node請求共用一個store實(shí)例(筆者易犯)。
本demo地址( 前端庫React+mobx+ReactRouter ):https://github.com/Penggggg/react-ssr
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家?! ?/p>
相關(guān)文章
React使用highlight.js Clipboard.js實(shí)現(xiàn)代碼高亮復(fù)制
這篇文章主要為大家介紹了React使用highlight.js Clipboard.js實(shí)現(xiàn)代碼高亮復(fù)制功能示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-04-04nodejs和react實(shí)現(xiàn)即時通訊簡易聊天室功能
這篇文章主要介紹了nodejs和react實(shí)現(xiàn)即時通訊簡易聊天室功能,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價值,需要的朋友可以參考下2019-08-08react如何實(shí)現(xiàn)側(cè)邊欄聯(lián)動頭部導(dǎo)航欄效果
這篇文章主要介紹了react如何實(shí)現(xiàn)側(cè)邊欄聯(lián)動頭部導(dǎo)航欄效果,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-03-03react 報錯Module build failed: Browserslis
這篇文章主要介紹了react 報錯Module build failed: BrowserslistError: Unknown browser query `dead`問題的解決方法,需要的朋友可以參考下2023-06-06利用React實(shí)現(xiàn)一個有點(diǎn)意思的電梯小程序
這篇文章主要為大家詳解介紹了如何利用React實(shí)現(xiàn)一個有點(diǎn)意思的電梯小程序,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起了解一下2022-08-08