欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

詳解基于React.js和Node.js的SSR實(shí)現(xiàn)方案

 更新時(shí)間:2019年03月21日 11:06:54   作者:銅板街技術(shù)  
這篇文章主要介紹了詳解基于React.js和Node.js的SSR實(shí)現(xiàn)方案,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧

基礎(chǔ)概念

SSR:即服務(wù)端渲染(Server Side Render) 傳統(tǒng)的服務(wù)端渲染可以使用Java,php 等開(kāi)發(fā)語(yǔ)言來(lái)實(shí)現(xiàn),隨著 Node.js 和相關(guān)前端領(lǐng)域技術(shù)的不斷進(jìn)步,前端同學(xué)也可以基于此完成獨(dú)立的服務(wù)端渲染。

過(guò)程:瀏覽器發(fā)送請(qǐng)求 -> 服務(wù)器運(yùn)行 react代碼生成頁(yè)面 -> 服務(wù)器返回頁(yè)面 -> 瀏覽器下載HTML文檔 -> 頁(yè)面準(zhǔn)備就緒 即:當(dāng)前頁(yè)面的內(nèi)容是服務(wù)器生成好給到瀏覽器的。

 

對(duì)應(yīng)CSR:即客戶端渲染(Client Side Render) 過(guò)程:瀏覽器發(fā)送請(qǐng)求 -> 服務(wù)器返回空白 HTML(HTML里包含一個(gè)root節(jié)點(diǎn)和js文件) -> 瀏覽器下載js文件 -> 瀏覽器運(yùn)行react代碼 -> 頁(yè)面準(zhǔn)備就緒 即:當(dāng)前頁(yè)面的內(nèi)容是js渲染出來(lái)

如何區(qū)分頁(yè)面是否服務(wù)端渲染: 右鍵點(diǎn)擊 -> 顯示網(wǎng)頁(yè)源代碼,如果頁(yè)面上的內(nèi)容在HTML文檔里,是服務(wù)端渲染,否則就是客戶端渲染。

對(duì)比

  • CSR:首屏渲染時(shí)間長(zhǎng),react代碼運(yùn)行在瀏覽器,消耗的是瀏覽器的性能
  • SSR:首屏渲染時(shí)間短,react代碼運(yùn)行在服務(wù)器,消耗的是服務(wù)器的性能

為什么要用服務(wù)端渲染

首屏加載時(shí)間優(yōu)化,由于SSR是直接返回生成好內(nèi)容的HTML,而普通的CSR是先返回空白的HTML,再由瀏覽器動(dòng)態(tài)加載JavaScript腳本并渲染好后頁(yè)面才有內(nèi)容;所以SSR首屏加載更快、減少白屏的時(shí)間、用戶體驗(yàn)更好。

SEO (搜索引擎優(yōu)化),搜索關(guān)鍵詞的時(shí)候排名,對(duì)大多數(shù)搜索引擎,不識(shí)別JavaScript 內(nèi)容,只識(shí)別 HTML 內(nèi)容。 (注:原則上可以不用服務(wù)端渲染時(shí)最好不用,所以如果只有 SEO 要求,可以用預(yù)渲染等技術(shù)去替代)

構(gòu)建一個(gè)服務(wù)端渲染的項(xiàng)目

(1) 使用 Node.js 作為服務(wù)端和客戶端的中間層,承擔(dān) proxy代理,處理cookie等操作。

(2) hydrate 的使用:在有服務(wù)端渲染情況下,使用hydrate代替render,它的作用主要是將相關(guān)的事件注水進(jìn)HTML頁(yè)面中(即:讓React組件的數(shù)據(jù)隨著HTML文檔一起傳遞給瀏覽器網(wǎng)頁(yè)),這樣可以保持服務(wù)端數(shù)據(jù)和瀏覽器端一致,避免閃屏,使第一次加載體驗(yàn)更高效流暢。

 ReactDom.hydrate(<App />, document.getElementById('root'));

(3) 服務(wù)端代碼webpack編譯:通常會(huì)建一個(gè)webpack.server.js文件,除了常規(guī)的參數(shù)配置外,還需要設(shè)置target參數(shù)為'node'。

const serverConfig = {
 target: 'node',
 entry: './src/server/index.js',
 output: {
 filename: 'bundle.js',
 path: path.resolve(__dirname, '../dist')
 },
 externals: [nodeExternals()],
 module: {
 rules: [{
  test: /\.js?$/,
  loader: 'babel-loader',
  exclude: [
  path.join(__dirname, './node_modules')
  ]
 }
 ...
 ]
 }
 (此處省略樣式打包,代碼壓縮,運(yùn)行壞境配置等等...)
 ...
};

(4) 使用react-dom/server下的 renderToString方法在服務(wù)器上把各種復(fù)雜的組件和代碼轉(zhuǎn)化成 HTML 字符串返回到瀏覽器,并在初始請(qǐng)求時(shí)發(fā)送標(biāo)記以加快頁(yè)面加載速度,并允許搜索引擎抓取頁(yè)面以實(shí)現(xiàn)SEO目的。

const render = (store, routes, req, context) => {
 const content = renderToString((
 <Provider store={store}>
  <StaticRouter location={req.path} context={context}>
  <div>
   {renderRoutes(routes)}
  </div>
  </StaticRouter>
 </Provider>
 ));
 return `
 <html>
  <head>
  <title>ssr</title>
  </head>
  <body>
  <div id='root'>${content}</div>
  <script src='/index.js'></script>
  </body>
 </html>
 `;
}
app.get('*', function (req, res) {
 ...
 const html = render(store, routes, req, context);
 res.send(html);
});

與renderToString類似功能的還有: i. renderToStaticMarkup:區(qū)別在于renderToStaticMarkup 渲染出的是不帶data-reactid的純HTML,在JavaScript加載完成后因?yàn)椴徽J(rèn)識(shí)之前服務(wù)端渲染的內(nèi)容導(dǎo)致重新渲染(可能頁(yè)面會(huì)閃一下)。

ii. renderToNodeStream:將React元素渲染為其初始HTML,返回一個(gè)輸出HTML字符串的可讀流。

iii. renderToStaticNodeStream:與renderToNodeStream此類似,除了這不會(huì)創(chuàng)建React在內(nèi)部使用的額外DOM屬性,例如data-reactroot。

(5) 使用redux 承擔(dān)數(shù)據(jù)準(zhǔn)備,狀態(tài)維護(hù)的職責(zé),通常搭配react-redux, redux-thunk(中間件:發(fā)異步請(qǐng)求用到action)使用。(本猿目前使用比較多是就是Redux和Mobx,這里以Redux為例)。 A. 創(chuàng)建store(服務(wù)器每次請(qǐng)求都要?jiǎng)?chuàng)建一次,客戶端只創(chuàng)建一次):

const reducer = combineReducers({
 home: homeReducer,
 page1: page1Reducer,
 page2: page2Reducer
});

export const getStore = (req) => {
 return createStore(reducer, applyMiddleware(thunk.withExtraArgument(serverAxios(req))));
}

export const getClientStore = () => {
 return createStore(reducer, window.STATE_FROM_SERVER, applyMiddleware(thunk.withExtraArgument(clientAxios)));
}

B. action: 負(fù)責(zé)把數(shù)據(jù)從應(yīng)用傳到store,是store數(shù)據(jù)的唯一來(lái)源

export const getData = () => {
 return (dispatch, getState, axiosInstance) => {
 return axiosInstance.get('interfaceUrl/xxx')
  .then((res) => {
  dispatch({
   type: 'HOME_LIST',
   list: res.list
  })
  });
 }
}

C. reducer:接收舊的state和action,返回新的state,響應(yīng)actions并發(fā)送到store。

export default (state = { list: [] }, action) => {
 switch(action.type) {
 case 'HOME_LIST':
  return {
  ...state,
  list: action.list
  }
 default:
  return state;
 }
}
export default (state = { list: [] }, action) => {
 switch(action.type) {
 case 'HOME_LIST':
  return {
  ...state,
  list: action.list
  }
 default:
  return state;
 }
} 

D. 使用react-redux的connect,Provider把組件和store連接起來(lái)

Provider 將之前創(chuàng)建的store作為prop傳給Provider

const content = renderToString((
 <Provider store={store}>
 <StaticRouter location={req.path} context={context}>
  <div>
  {renderRoutes(routes)}
  </div>
 </StaticRouter>
 </Provider>
)); 

connect([mapStateToProps],[mapDispatchToProps],[mergeProps], [options])接收四個(gè)參數(shù) 常用的是前兩個(gè)屬性 mapStateToProps函數(shù)允許我們將store中的數(shù)據(jù)作為props綁定到組件上mapDispatchToProps將action作為props綁定到組件上

 connect(mapStateToProps(),mapDispatchToProps())(MyComponent)

(6) 使用react-router承擔(dān)路由職責(zé) 服務(wù)端路由不同于客戶端,它是無(wú)狀態(tài)的。React 提供了一個(gè)無(wú)狀態(tài)的組件StaticRouter,向StaticRouter傳遞當(dāng)前URL,調(diào)用ReactDOMServer.renderToString() 就能匹配到路由視圖。

服務(wù)端

import { StaticRouter } from 'react-router-dom';
import { renderRoutes } from 'react-router-config'
import routes from './router.js'

<StaticRouter location={req.path} context={{context}}>
{renderRoutes(routes)}
</StaticRouter> 

瀏覽器端

import { BrowserRouter } from 'react-router-dom';
import { renderRoutes } from 'react-router-config'
import routes from './router.js'

<BrowserRouter>
 {renderRoutes(routes)}
</BrowserRouter>

當(dāng)瀏覽器的地址欄發(fā)生變化的時(shí)候,前端會(huì)去匹配路由視圖,同時(shí)由于req.path發(fā)生變化,服務(wù)端匹配到路由視圖,這樣保持了前后端路由視圖的一致,在頁(yè)面刷新時(shí),仍然可以正常顯示當(dāng)前視圖。如果只有瀏覽器端路由,而且是采用BrowserRouter,當(dāng)頁(yè)面地址發(fā)生變化后去刷新頁(yè)面時(shí),由于沒(méi)有對(duì)應(yīng)的HTML,會(huì)導(dǎo)致頁(yè)面找不到,但是加了服務(wù)端路由后,刷新發(fā)生時(shí)服務(wù)端會(huì)返回一個(gè)完整的html給客戶端,頁(yè)面仍然正常顯示。 推薦使用 react-router-config插件,然后如上代碼在StaticRouter和BrowserRouter標(biāo)簽的子元素里加renderRoutes(routes):建一個(gè)router.js文件

const routes = [{ component: Root,
 routes: [
 { path: '/',
  exact: true,
  component: Home,
  loadData: Home.loadData
 },
 { path: '/child/:id',
  component: Child,
  loadData: Child.loadData
  routes: [
  path: '/child/:id/grand-child',
  component: GrandChild,
  loadData: GrandChild.loadData
  ]
 }
 ]
}];

在瀏覽器端請(qǐng)求一個(gè)地址的時(shí)候,server.js 里在實(shí)際渲染前可以通過(guò)matchRouters 這種方式確定要渲染的內(nèi)容,調(diào)用loaderData函數(shù)進(jìn)行action派發(fā),返回promise->promiseAll->renderToString,最終生成HTML文檔返回。

import { matchRoutes } from 'react-router-config'
 const loadBranchData = (location) => {
 const branch = matchRoutes(routes, location.pathname)

 const promises = branch.map(({ route, match }) => {
  return route.loadData
  ? route.loadData(match)
  : Promise.resolve(null)
 })

 return Promise.all(promises)
}

(7) 寫(xiě)組件注意代碼同構(gòu)(即:一套R(shí)eact代碼在服務(wù)端執(zhí)行一次,在客戶端再執(zhí)行一次) 由于服務(wù)器端綁定事件是無(wú)效的,所以服務(wù)器返回的只有頁(yè)面樣式(&注水的數(shù)據(jù)),同時(shí)返回JavaScript文件,在瀏覽器上下載并執(zhí)行JavaScript時(shí)才能把事件綁上,而我們希望這個(gè)過(guò)程只需編寫(xiě)一次代碼,這個(gè)時(shí)候就會(huì)用到同構(gòu),服務(wù)端渲染出樣式,在客戶端執(zhí)行時(shí)綁上事件。

優(yōu)點(diǎn): 共用前端代碼,節(jié)省開(kāi)發(fā)時(shí)間 弊端: 由于服務(wù)器端和瀏覽器環(huán)境差異,會(huì)帶來(lái)一些問(wèn)題,如document等對(duì)象找不到,DOM計(jì)算報(bào)錯(cuò),前端渲染和服務(wù)端渲染內(nèi)容不一致等;前端可以做非常復(fù)雜的請(qǐng)求合并和延遲處理,但為了同構(gòu),所有這些請(qǐng)求都在預(yù)先拿到結(jié)果才會(huì)渲染。

以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。

相關(guān)文章

  • React Router v4 入坑指南(小結(jié))

    React Router v4 入坑指南(小結(jié))

    這篇文章主要介紹了React Router v4 入坑指南(小結(jié)),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2018-04-04
  • React捕獲并處理異常的方式

    React捕獲并處理異常的方式

    這篇文章主要給大家介紹了React優(yōu)雅的捕獲并處理渲染異常方式,文章通過(guò)代碼示例給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作有一定的幫助,需要的朋友可以參考下
    2023-11-11
  • React實(shí)現(xiàn)卡片拖拽效果流程詳解

    React實(shí)現(xiàn)卡片拖拽效果流程詳解

    這篇文章主要介紹了React Web開(kāi)發(fā)實(shí)戰(zhàn)示例,實(shí)現(xiàn)卡片拖拽效果,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)吧
    2022-11-11
  • forwardRef?中React父組件控制子組件的實(shí)現(xiàn)代碼

    forwardRef?中React父組件控制子組件的實(shí)現(xiàn)代碼

    forwardRef 用于拿到父組件傳入的 ref 屬性,這樣在父組件便能通過(guò) ref 控制子組件,這篇文章主要介紹了forwardRef?-?React父組件控制子組件的實(shí)現(xiàn)代碼,需要的朋友可以參考下
    2024-01-01
  • React使用refs操作DOM方法詳解

    React使用refs操作DOM方法詳解

    React核心就在于虛擬DOM,也就是在React中不總是直接操作頁(yè)面真實(shí)的DOM元素,并且結(jié)合Diffing算法,可以做到最小化頁(yè)面重繪,有些時(shí)候不可避免的我們需要一種方法可以操作我們定義的元素標(biāo)簽,并作出對(duì)應(yīng)的修改。在React中提供了一種訪問(wèn)DOM節(jié)點(diǎn)的方式,也就是這里的refs
    2022-11-11
  • React中的權(quán)限組件設(shè)計(jì)問(wèn)題小結(jié)

    React中的權(quán)限組件設(shè)計(jì)問(wèn)題小結(jié)

    這篇文章主要介紹了React中的權(quán)限組件設(shè)計(jì),整個(gè)過(guò)程也是遇到了很多問(wèn)題,本文主要來(lái)做一下此次改造工作的總結(jié),對(duì)React權(quán)限組件相關(guān)知識(shí)感興趣的朋友一起看看吧
    2022-07-07
  • React如何使用sortablejs實(shí)現(xiàn)拖拽排序

    React如何使用sortablejs實(shí)現(xiàn)拖拽排序

    這篇文章主要介紹了React如何使用sortablejs實(shí)現(xiàn)拖拽排序問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-01-01
  • react hook使用useState更新數(shù)組,無(wú)法更新問(wèn)題及解決

    react hook使用useState更新數(shù)組,無(wú)法更新問(wèn)題及解決

    這篇文章主要介紹了react hook使用useState更新數(shù)組,無(wú)法更新問(wèn)題及解決,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-03-03
  • 淺談React useDebounce 防抖原理

    淺談React useDebounce 防抖原理

    本文主要介紹了淺談React useDebounce 防抖原理,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2022-08-08
  • 解讀react的onClick自動(dòng)觸發(fā)等相關(guān)問(wèn)題

    解讀react的onClick自動(dòng)觸發(fā)等相關(guān)問(wèn)題

    這篇文章主要介紹了解讀react的onClick自動(dòng)觸發(fā)等相關(guān)問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-02-02

最新評(píng)論