日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 >

React服务端渲染实现(基于Dva)

發布時間:2025/3/20 47 豆豆
生活随笔 收集整理的這篇文章主要介紹了 React服务端渲染实现(基于Dva) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

React服務端渲染實現 (基于Dva)

功能

  • 基于 Dva 的 SSR 解決方案 (react-router-v4, redux, redux-saga)
  • 支持 Dynamic Import (不再使用Dva自帶的 dva/dynamic加載組件)
  • 支持 CSS Modules

SSR實現邏輯

概覽

.png)

上圖是SSR的運行時流程圖(暫時不考慮構建的問題)

圖中左側是瀏覽器端看到的頁面源碼。其中紅色框標識的3個部分,是SSR需要關注的重點內容。

  • 最簡單的是中間一個框,它是服務端渲染的App的內容部分。

  • 第一個是分片(splitting)代碼文件。即SSR Server必須要知道,瀏覽器要正確展示這個頁面,需要包含哪些分片的js代碼。 如果不計算并返回這個script標簽,那么瀏覽器render這個list 組建時,會發現這個組件不存在,還需要異步加載并re-render 頁面。

  • 最后一個框,是服務端返回的 window._preloadedState 即 全局狀態對象。瀏覽器端要使用這個對象對redux的store進行初始化。

收到客戶端的SSR請求后,SSR Server將依次執行如下五部操作:

  • 對請求的路徑,進行路由匹配;并 "獲取/加載"(獲取對應同步組件,加載對應異步組件) 所涉及的組件
  • // 初始化const history = createMemoryHistory();history.push(req.path);const initialState = {};const app = dva({history, initialState});app.router(router);const App = app.start();let routes = getRoutes(app);// 匹配路由,獲取需要加載的Route組件(包含Loadable組件)const matchedComponents = matchRoutes(routes, req.path).map(({route}) => {if (!route.component.preload) {// 同步組件return route.component;} else {// 異步組件return route.component.preload().then(res => res.default)}});const loadedComponents = await Promise.all(matchedComponents); 復制代碼
  • 對1中組件進行初始化(如需),進行接口請求,并等待請求返回。
  • 注: 需要進行數據初始化的組件,需要定義 static fetching 方法

    const actionList = loadedComponents.map(component => {if (component.fetching) {return component.fetching({...app._store,...component.props,path: req.path});} else {return null;}}); await Promise.all(actionList); 復制代碼
  • 調用 ReactDOMServer.renderString 渲染數據
  • // Render Dva App。同時使用Loadable.Capture 捕捉本次渲染包含的Loadable組件集合Array<String>。 const modules = []; const markup = renderToString(<Loadable.Capture report={module => modules.push(module)}><App location={req.path} context={{}}/></Loadable.Capture> );// 構造需要render的 script標簽。其中利用了react-loadable的webpack插件在構建過程中生成的module字典 let bundles = getBundles(moduleDict, modules); let scripts = bundles.filter(bundle => bundle.file.endsWith('.js')); let scriptMarkups = scripts.map(bundle => {return `<script src="/public/${bundle.file}"></script>` }).join('\n'); 復制代碼

    Loadable 的相關概念和用法,請參考 github: react-loadable

    Code Splitting

  • 獲取preloadedState
  • const preloadedState = app._store.getState(); 復制代碼
  • 拼裝Html,并返回
  • res.send(` <!DOCTYPE html> <html><head><title>React Server Side Demo With Dva</title><link href="/public/style.css" rel="stylesheet"></head><body><div id="app">${markup}</div><script>window.__PRELOADED_STATE__ = ${JSON.stringify(preloadedState).replace(/</g, '\\\u003c')}</script><script src="/public/main.js"></script>${scriptMarkups}</body> </html> `); 復制代碼

    如何支持Dva

    本節分幾個部分:

  • 如何既支持 dva/dynamic 又支持 SSR
  • SSR Server 端如何支持 Dva
  • SSR Client 端如何支持 Dva
  • 如何既支持 dva/dynamic 又支持 SSR

    之前使用dva的Code Splitting功能時,用的是 dva/dynamic。示例代碼如下:

    import dynamic from 'dva/dynamic';const UserPageComponent = dynamic({app,models: () => [import('./models/users'),],component: () => import('./routes/UserPage'), });復制代碼

    它的問題是不支持SSR。解決方法是使用 react-loadable 代替 dva/dynamic。為了不影響dva的功能, 我們需要了解 dva/dynamic 除了實現了加載組件之外還實現了哪些功能。

    通過查閱dva源碼,發現 dva/dynamic 額外實現的功能比較純粹,就是 register model

    // packages/dva/src/dynamic.jsconst cached = {};function registerModel(app, model) {model = model.default || model;if (!cached[model.namespace]) {app.model(model);cached[model.namespace] = 1;} }// ..... 省略部分代碼export default function dynamic(config) {const { app, models: resolveModels, component: resolveComponent } = config;return asyncComponent({resolve: config.resolve || function () {const models = typeof resolveModels === 'function' ? resolveModels() : [];const component = resolveComponent();return new Promise((resolve) => {Promise.all([...models, component]).then((ret) => {if (!models || !models.length) {return resolve(ret[0]);} else {const len = models.length;ret.slice(0, len).forEach((m) => {m = m.default || m;if (!Array.isArray(m)) {m = [m];}// 注冊 modelm.map(_ => registerModel(app, _));});resolve(ret[len]);}});});},...config,}); }復制代碼

    因此,我們需要在 react-loadable 的基礎上,增加 registerModel 功能,且需要自己維護 cached model 這個對象。

    為什么選擇 react-loadable ?

    通過翻閱若干個支持SSR Code Splitting的Repo,只有 react-loadable 比較好的支持 "多個文件加載"。

    下面是react-loadable 的基本用法:

    Loadable({loader: () => import('./components/Bar'),loading: Loading,timeout: 10000, // 10 seconds });復制代碼

    不難發現, 這是不能夠完全匹配 dva/dynamic 的能力的。因為在Dva里,有model這個概念。 我們不僅需要加載UI組件本身,還需要加載它所依賴的model文件。而react-loadable 可以很好的支持這個特性。

    下面是 react-loadable 的 Loadable.Map 用法

    Loadable.Map({loader: {Bar: () => import('./Bar'),i18n: () => fetch('./i18n/bar.json').then(res => res.json()),},render(loaded, props) {let Bar = loaded.Bar.default;let i18n = loaded.i18n;return <Bar {...props} i18n={i18n}/>;}, }); 復制代碼

    經過修改,我們可以得到兼容dva的dynamic方案。 例如,有一個頁面叫做 Grid。它依賴2個model,分別是 grid 和 user。

    Loadable.Map({loader: {Grid: () => import('./routes/Grid.js'),grid: () => import('./models/grid.js'),user: () => import('./models/user.js'),},delay: 200,timeout: 1000,loading: Loading,render(loaded, props) {let Grid = loaded["Grid"].default;let grid = loaded["grid"].default;let user = loaded["grid"].default;registerModel(app, grid);registerModel(app, user);return <Grid {...props} />;},});復制代碼

    對于復雜的項目,可能有很多route配置,寫上面這個配置項代碼較多。我們可以考慮對其進行封裝。 基于此,我們可以考慮實現 dynamicLoader 方法。

    const dynamicLoader = (app, modelNameList, componentName) => {let loader = {};let models = [];let fn = (path, prefix) => {return () => import(`./${prefix}/${path}`);};if (modelNameList && modelNameList.length > 0) {for (let i in modelNameList) {if (modelNameList.hasOwnProperty(i)) {let model = modelNameList[i];if (loader[model] === undefined) {loader[model] = fn(model, 'models');models.push(model);}}}}loader[componentName] = fn(componentName, 'routes');return Loadable.Map({loader: loader,loading: Loading,render(loaded, props) {let C = loaded[componentName].default;for (let i in models) {if (models.hasOwnProperty(i)) {let model = models[i];if (loaded[model] && getApp()) {registerModel(app, loaded[model]);}}}return <C {...props}/>;},}); };// 使用const routes = [{path: '/popular/:id',component: dynamicLoader(app, ['grid'], 'Grid') }];復制代碼

    但是,上述代碼在 SSR Server端是無法工作的。

    首先,react-loadable 需要在webpack打包過程中生成Loadable組件的數據字典。 SSR Server 需要利用這個字典的信息生成 分片js代碼的 script 標簽。

    字典文件示例:

    // react-loadable.json{"./routes/Grid.js": [{"id": 141,"name": "./src/routes/Grid.js","file": "0.js","publicPath": "/public/0.js"}],"lodash/isArray": [{"id": 296,"name": "./node_modules/lodash/isArray.js","file": "0.js","publicPath": "/public/0.js"},{"id": 296,"name": "./node_modules/lodash/isArray.js","file": "1.js","publicPath": "/public/1.js"}]//... 以下省略 }復制代碼

    實際使用發現,上述代碼 dynamicLoader 無法生成正確的字典。

    后經過Debug發現,問題根源是代碼中使用了帶參數的 import。即 import(./${prefix}/${path}), 而webpack 在構建過程中無法靜態獲取Loadable組件的路徑。因此,不能使用帶參數的 import。

    最終的方案是,定義路由配置文件 routes.json。然后編寫一個路由生成器,生成需要的路由文件。

    示例的routes.json 文件如下:

    [{"path": "/","exact": true,"dva_route": "./routes/Home.js","dva_models": []},{"path": "/popular/:id","dva_route": "./routes/Grid.js","dva_models": ["./models/grid.js"]},{"path": "/topic","dva_route": "./routes/Topic.js","dva_models": []} ]復制代碼

    到此,我們就完成了對于dva/dynamic 和 SSR 的支持。

    SSR Server 端如何支持 Dva
  • app.start
  • 默認情況下:

    app.start('#root'); 復制代碼

    server 端應該不加參數

    // 官方示例 import { IntlProvider } from 'react-intl'; ... const App = app.start(); ReactDOM.render(<IntlProvider><App /></IntlProvider>, htmlElement);// 本實現的示例const App = app.start(); const markup = renderToString(<Loadable.Capture report={module => modules.push(module)}><App location={req.path} context={{}}/></Loadable.Capture>); 復制代碼
  • model register
  • const matchedComponents = matchRoutes(routes, req.path).map(({route}) => {if (!route.component.preload) {return route.component;} else {// 加載Loadable組件return route.component.preload().then(res => {if (res.default) {// Loadable 組件return res.default;} else {// Loadable.Map 組件let result;for (let i in res) {if (res.hasOwnProperty(i)) {if (res[i].default.hasOwnProperty('namespace')) {// model 組件registerModel(app, res[i]);} else {// route 組件result = res[i].default;}}}return result;}})}});復制代碼
  • 調用組件初始化方法fetching時,需要傳入 dispatch。而全局的dispatch對象在 app._store 里
  • const actionsList = loadedComponents.map(component => {if (component.fetching) {return component.fetching({...app._store,...component.props,path: req.path});} else {return null;}});// 示例 fetching 方法static fetching({dispatch, path}) {let language = path.substr("/popular/".length);return [dispatch({type: 'grid/init', payload: {language}}),]; }復制代碼
    客戶端如何支持 Dva
  • render
  • Loadable.preloadReady().then(() => {const App = app.start();hydrate(<App/>,document.getElementById('app')); });復制代碼
  • 組件的初始化數據方法 fetching
  • 由于一個route 可能需要依賴多個model作為數據源。故返回一個dispatch 的數組。這樣server就可以通過多個接口拿數據。

    static fetching({dispatch, path, params}) {let language = path.substr("/popular/".length);return [dispatch({type: 'grid/init', payload: {language}}),dispatch({type: 'user/fetch', payload: {userId: params.userId}})]; }復制代碼

    使用

    • npm install
    • npm run dev
    • view localhost:3000

    轉載于:https://juejin.im/post/5bc40ad6e51d450e4d3031cf

    總結

    以上是生活随笔為你收集整理的React服务端渲染实现(基于Dva)的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

    主站蜘蛛池模板: 久久午夜福利电影 | 四虎在线看片 | 国产精品骚 | 亚洲12p | 先锋资源网av | 影音先锋中文在线 | 久久午夜电影 | 99极品视频 | ,午夜性刺激免费看视频 | 亚洲乱码国产乱码精品天美传媒 | 成人高清网站 | 国产精品影院在线观看 | 久久国产精品国产精品 | 精品国产乱码久久久久久蜜臀网站 | 裸体按摩www性xxxcom | 韩国一级淫片 | 国产片自拍 | 日韩精品啪啪 | 97成人资源站 | 伊人色图| 免费成人高清在线视频 | 欧美一区二区在线观看视频 | 四虎国产成人精品免费一女五男 | 美女一区二区三区四区 | 一级做a爰片久久毛片 | 操天天 | 91极品视频| 欧美一区二区三区系列电影 | 日韩欧美自拍偷拍 | 亚洲熟妇无码一区二区三区导航 | 尤物在线免费视频 | 一级黄色性视频 | 18在线观看免费入口 | av免费一区 | 国产精品无码无卡无需播放器 | 叶全真三级| 欧美bdsm调教视频 | 91免费国产| 无码国产精品一区二区高潮 | av免费的 | h小视频在线观看 | 天堂伊人网 | 一本久久精品一区二区 | 97久久久久久久久久 | h网站在线播放 | 韩国性经典xxxxhd | 天堂va蜜桃一区二区三区漫画版 | 亚洲三级中文字幕 | 五月天色网站 | 黄色免费网站在线看 | 日韩欧美激情视频 | 国产 日韩 欧美 综合 | 好男人网站 | 99在线无码精品入口 | 又粗又大又硬又长又爽 | 色播激情| 精品96久久久久久中文字幕无 | 美女操操操 | 亚洲爱爱爱| japanese国产打屁股网站 | 性感美女福利视频 | 91九色在线观看 | 日韩精品中文字幕在线播放 | 制服诱惑一区二区三区 | 98国产精品| 夜夜爽天天爽 | 67194成人在线 | 中文字幕黄色 | 亚洲精品777| 你懂的视频在线播放 | 亚欧美精品| 视频免费观看在线 | 一区二区有码 | 中文字幕一区二区人妻电影 | a片在线免费观看 | 日本天堂网在线 | 成人字幕 | 色又黄又爽 | 污污的视频在线观看 | 久久国产精品二区 | 久久人人爽人人爽人人 | 奇米影视播放器 | 久久久精品美女 | 99精品无码一区二区 | 国产综合久久久 | 精品国产丝袜一区二区三区乱码 | 青青草视频偷拍 | 免费99精品国产自在在线 | 91欧美日韩麻豆精品 | 9999久久久久| 91精品国产99久久久久久红楼 | 日本韩国视频 | 免费看国产视频 | 欧美日韩色 | 亚洲少妇18p | 国精无码欧精品亚洲一区蜜桃 | 屁屁影院国产第一页 | 97色伦97色伦国产欧美空 | www.色播.com|