1. <ul id="0c1fb"></ul>

      <noscript id="0c1fb"><video id="0c1fb"></video></noscript>
      <noscript id="0c1fb"><listing id="0c1fb"><thead id="0c1fb"></thead></listing></noscript>

      99热在线精品一区二区三区_国产伦精品一区二区三区女破破_亚洲一区二区三区无码_精品国产欧美日韩另类一区

      RELATEED CONSULTING
      相關(guān)咨詢(xún)
      選擇下列產(chǎn)品馬上在線溝通
      服務(wù)時(shí)間:8:30-17:00
      你可能遇到了下面的問(wèn)題
      關(guān)閉右側(cè)工具欄

      新聞中心

      這里有您想知道的互聯(lián)網(wǎng)營(yíng)銷(xiāo)解決方案
      reactkoarematch怎么打造一套服務(wù)端渲染架子

      這篇文章主要介紹了react koa rematch怎么打造一套服務(wù)端渲染架子,具有一定借鑒價(jià)值,感興趣的朋友可以參考下,希望大家閱讀完這篇文章之后大有收獲,下面讓小編帶著大家一起了解一下。

      創(chuàng)新互聯(lián)建站專(zhuān)注于鐘祥網(wǎng)站建設(shè)服務(wù)及定制,我們擁有豐富的企業(yè)做網(wǎng)站經(jīng)驗(yàn)。 熱誠(chéng)為您提供鐘祥營(yíng)銷(xiāo)型網(wǎng)站建設(shè),鐘祥網(wǎng)站制作、鐘祥網(wǎng)頁(yè)設(shè)計(jì)、鐘祥網(wǎng)站官網(wǎng)定制、成都微信小程序服務(wù),打造鐘祥網(wǎng)絡(luò)公司原創(chuàng)品牌,更為您提供鐘祥網(wǎng)站排名全網(wǎng)營(yíng)銷(xiāo)落地服務(wù)。

      前言

      本次講述的內(nèi)容主要是 react 與 koa 搭建的一套 ssr 框架,是在別人造的輪子上再添加了一些自己的想法和完善一下自己的功能。

      本次用到的技術(shù)為: react | rematch | react-router | koa

      react服務(wù)端渲染優(yōu)勢(shì)

      SPA(single page application)單頁(yè)應(yīng)用雖然在交互體驗(yàn)上比傳統(tǒng)多頁(yè)更友好,但它也有一個(gè)天生的缺陷,就是對(duì)搜索引擎不友好,不利于爬蟲(chóng)爬取數(shù)據(jù)(雖然聽(tīng)說(shuō)chrome能夠異步抓取spa頁(yè)面數(shù)據(jù)了);

      SSR與傳統(tǒng) SPA(Single-Page Application - 單頁(yè)應(yīng)用程序)相比,服務(wù)器端渲染(SSR)的優(yōu)勢(shì)主要在于:更好的 SEO 和首屏加載效果。

      在 SPA 初始化的時(shí)候內(nèi)容是一個(gè)空的 div,必須等待 js 下載完才開(kāi)始渲染頁(yè)面,但 SSR 就可以做到直接渲染html結(jié)構(gòu),極大地優(yōu)化了首屏加載時(shí)間,但上帝是公平的,這種做法也增加了我們極大的開(kāi)發(fā)成本,所以大家必須綜合首屏?xí)r間對(duì)應(yīng)用程序的重要程度來(lái)進(jìn)行開(kāi)發(fā),或許還好更好地代替品(骨架屏)。

      react服務(wù)端渲染流程

      組件渲染

      首先肯定是根組件的render,而這一部分和SPA有一些小不同。

      使用 ReactDOM.render() 來(lái)混合服務(wù)端渲染的容器已經(jīng)被棄用,并且會(huì)在React 17 中刪除。使用hydrate() 來(lái)代替。

      hydrate與 render 相同,但用于混合容器,該容器的HTML內(nèi)容是由 ReactDOMServer 渲染的。 React 將嘗試將事件監(jiān)聽(tīng)器附加到現(xiàn)有的標(biāo)記。

      hydrate 描述的是 ReactDOM 復(fù)用 ReactDOMServer 服務(wù)端渲染的內(nèi)容時(shí)盡可能保留結(jié)構(gòu),并補(bǔ)充事件綁定等 Client 特有內(nèi)容的過(guò)程。

      import React from 'react';
      import ReactDOM from 'react-dom';
      
      ReactDOM.hydrate(, document.getElementById('app'));

      在服務(wù)端中,我們可以通過(guò) renderToString 來(lái)獲取渲染的內(nèi)容來(lái)替換 html 模版中的東西。

      const jsx = 
        
          
        
        
      const html = ReactDOMServer.renderToString(jsx);
      
      let ret = `
        
          
          
           
           
          
          
           ${html}
              `; return ret;

      服務(wù)端返回替換后的 html 就完成了本次組件服務(wù)端渲染。

      路由同步渲染

      在項(xiàng)目中避免不了使用路由,而在SSR中,我們必須做到路由同步渲染。

      首先我們可以把路由拆分成一個(gè)組件,服務(wù)端入口和客戶端都可以分別引用。

      function AppRoutes({ context, initialData }: any) {
       return (
        
         {
          routes.map((d: any) => (
           
            key={d.path}
            exact={d.exact}
            path={d.path}
            init={d.init || ''}
            component={d.component}
           />
          ))
         }
         
        
       );
      }

      (routes.js)

      export const routes = [
       {
        path: '/Home',
        component: Home,
        init: Home.init,
        exact: true,
       },
       {
        path: '/Hello',
        component: Hello,
        init: Hello.init,
        exact: true,
       }
      ];

      這樣我們的路由基本定義完了,然后客戶端引用還是老規(guī)矩,和SPA沒(méi)什么區(qū)別

      import { BrowserRouter as Router } from 'react-router-dom';
      import AppRoutes from './AppRoutes';
      class App extends Component> {
      ...
       render() {
        return (
        
         
        
        );
       }
      }

      在服務(wù)端中,需要使用將BrowserRouter 替換為 StaticRouter 區(qū)別在于,BrowserRouter 會(huì)通過(guò)HTML5 提供的 history API來(lái)保持頁(yè)面與URL的同步,而StaticRouter 則不會(huì)改變URL,當(dāng)一個(gè) 匹配時(shí),它將把 context 對(duì)象傳遞給呈現(xiàn)為 staticContext 的組件。

      const jsx = 
        
          
        
        
      const html = ReactDOMServer.renderToString(jsx);

      至此,路由的同步已經(jīng)完成了。

      redux同構(gòu)

      在寫(xiě)這個(gè)之前必須先了解什么是注水和脫水,所謂脫水,就是服務(wù)器在構(gòu)建 HTML 之前處理一些預(yù)請(qǐng)求,并且把數(shù)據(jù)注入html中返回給瀏覽器。而注水就是瀏覽器把這些數(shù)據(jù)當(dāng)初始數(shù)據(jù)來(lái)初始化組件,以完成服務(wù)端與瀏覽器端數(shù)據(jù)的統(tǒng)一。

      組件配置

      在組件內(nèi)部定義一個(gè)靜態(tài)方法

      class Home extends React.Component {
      ...
       public static init(store:any) {
        return store.dispatch.Home.incrementAsync(5);
       }
       componentDidMount() {
        const { incrementAsync }:any = this.props;
        incrementAsync(5);
       }
       render() {
       ...
       }
      }
      
      const mapStateToProps = (state:any) => {
       return {
        count: state.Home.count
       };
      };
      
      const mapDispatchToProps = (dispatch:any) => ({
       incrementAsync: dispatch.Home.incrementAsync
      });
      export default connect(
       mapStateToProps,
       mapDispatchToProps
      )(Home);

      由于我這邊使用的是rematch,所以我們的方法都寫(xiě)在model中。

      const Home: ModelConfig= {
       state: {
        count: 1
       }, 
       reducers: {
        increment(state, payload) {
         return {
          count: payload
         };
        }
       },
       effects: {
        async incrementAsync(payload, rootState) {
         await new Promise((resolve) => setTimeout(resolve, 1000));
         this.increment(payload);
        }
       }
      };
      export default Home;

      然后通過(guò)根 store 中進(jìn)行 init。

      import { init } from '@rematch/core';
      import models from './models';
      
      const store = init({
       models: {...models}
      });
      
      export default store;

      然后可以綁定在我們 redux 的 Provider 中。

      
        
         
        
      

      路由方面我們需要把組件的 init 方法綁定在路由上方便服務(wù)端請(qǐng)求數(shù)據(jù)時(shí)使用。

      
         {
          routes.map((d: any) => (
           
            key={d.path}
            exact={d.exact}
            path={d.path}
            init={d.init || ''}
            component={d.component}
           />
          ))
         }
         
        

      以上就是客戶端需要進(jìn)行的操作了,因?yàn)?SSR 中我們服務(wù)端也需要進(jìn)行數(shù)據(jù)的操作,所以為了解耦,我們就新建另一個(gè) ServiceStore 來(lái)提供服務(wù)端使用。

      在服務(wù)端構(gòu)建 Html 前,我們必須先執(zhí)行完當(dāng)前組件的 init 方法。

      import { matchRoutes } from 'react-router-config';
      // 用matchRoutes方法獲取匹配到的路由對(duì)應(yīng)的組件數(shù)組
      const matchedRoutes = matchRoutes(routes, url);
      const promises = [];
      for (const item of matchedRoutes) {
       if (item.route.init) {
        const promise = new Promise((resolve, reject) => {
         item.route.init(serverStore).then(resolve).catch(resolve);
        });
        promises.push(promise);
       }
      }
      return Promise.all(promises);

      注意我們新建一個(gè) Promise 的數(shù)組來(lái)放置 init 方法,因?yàn)橐粋€(gè)頁(yè)面可能是由多個(gè)組件組成的,我們必須等待所有的 init 執(zhí)行完畢后才執(zhí)行相應(yīng)的 html 構(gòu)建。

      現(xiàn)在可以得到的數(shù)據(jù)掛在 window 下,等待客戶端的讀取了。

      let ret = `
         
          
          
           
           
          
          
           ${html}
                       `;

      然后在我們的客戶端中讀取剛剛的 initialStore 數(shù)據(jù)

      ....
      const defaultStore = window.__INITIAL_STORE__ || {};
      const store = init({
       models,
       redux: {
        initialState: defaultStore
       }
      });
      
      export default store;

      至此,redux的同構(gòu)基本完成了,因?yàn)檫叿南薅ǎ揖蜎](méi)有貼太多代碼,大家可以到文章底部的點(diǎn)擊我的倉(cāng)庫(kù)看看具體代碼哈,然后我再說(shuō)說(shuō)幾個(gè) redux 同構(gòu)中比較坑的地方。

      1.使用不了 @loadable/component 異步組件加載,因?yàn)椴荒塬@取組件內(nèi)部方法。 解決的辦法就是在預(yù)請(qǐng)求我們不放在組件中,直接拆分出來(lái)寫(xiě)在一個(gè)文件中統(tǒng)一管理,但我嫌這樣不好管理就放棄了異步加載組件了。

      2.在客戶端渲染的時(shí)候如果數(shù)據(jù)一閃而過(guò),那就是初始化數(shù)據(jù)并沒(méi)有成功,當(dāng)時(shí)這里卡了我好久喔。

      css樣式直出

      首先,服務(wù)端渲染的時(shí)候,解析 css 文件,不能使用 style-loader 了,要使用 isomorphic-style-loader 。使用 style-loader 的時(shí)候會(huì)有一閃而過(guò)的現(xiàn)象,是因?yàn)闉g覽器是需要加載完 css 才能把樣式加上。為了解決這樣的問(wèn)題,我們可以通過(guò)isomorphic-style-loader 在組件加載的時(shí)候把 css 放置在全局的 context 里面,然后在服務(wù)端渲染時(shí)候提取出來(lái),插入到返回的HTML中的 style 標(biāo)簽。

      組件的改造

      import withStyles from 'isomorphic-style-loader/withStyles';
      
      @withStyles(style)
      class Home extends React.Component {
      ...
       render() {
        const {count}:any = this.props;
        return (
        ...
        );
       }
      }
      const mapStateToProps = (state:any) => {
       return {
        count: state.Home.count
       };
      };
      
      const mapDispatchToProps = (dispatch:any) => ({
       incrementAsync: dispatch.Home.incrementAsync
      });
      export default connect(
       mapStateToProps,
       mapDispatchToProps
      )(Home);

      withStyle 是一個(gè)柯里化函數(shù),返回的是一個(gè)新的組件,并不影響 connect 函數(shù),當(dāng)然你也可以像 connect 一樣的寫(xiě)法。withStyle 主要是為了把 style 插入到全局的 context 里面。

      根組件的修改

      import StyleContext from 'isomorphic-style-loader/StyleContext';

      const insertCss = (...styles:any) => {
       const removeCss = styles.map((style:any) => style._insertCss());
       return () => removeCss.forEach((dispose:any) => dispose());
      };
      
      ReactDOM.hydrate(
        
          
           
          
        ,
        elRoot
      );

      這一部分主要是引入了 StyleContext 初始化根部的context,并且定義好一個(gè) insertCss 方法,在組件 withStyle 中觸發(fā)。

      部分 isomorphic-style-loader 源碼

      ...
      function WithStyles(props, context) {
        var _this;
        _this = _React$PureComponent.call(this, props, context) || this;
        _this.removeCss = context.insertCss.apply(context, styles);
        return _this;
       }
      
       var _proto = WithStyles.prototype;
      
       _proto.componentWillUnmount = function componentWillUnmount() {
        if (this.removeCss) {
         setTimeout(this.removeCss, 0);
        }
       };
      
       _proto.render = function render() {
        return React.createElement(ComposedComponent, this.props);
       };
       ...

      可以看到 context 中的 insert 方法就是根組件中的 定義好的 insert 方法,并且在 componentWillUnmount 這個(gè)銷(xiāo)毀的生命周期中把之前 style 清除掉。而 insert 方法主要是為了給當(dāng)前的 style 定義好id并且嵌入,這里就不展開(kāi)說(shuō)明了,有興趣的可以看一下源碼。

      服務(wù)端中獲取定義好的css

      const css = new Set(); // CSS for all rendered React components
      
      const insertCss = (...styles :any) => {
       return styles.forEach((style:any) => css.add(style._getCss()));
      };
      
      const extractor = new ChunkExtractor({ statsFile: this.statsFile });
      
      const jsx = extractor.collectChunks(
       
        
          
           
          
        
       
      );
      
      const html = ReactDOMServer.renderToString(jsx);
      const cssString = Array.from(css).join('');
      ...

      其中 cssString 就是我們最后獲取到的 css 內(nèi)容,我們可以像 html 替換一樣把 css 嵌入到 html 中。

      let ret = `
         
          
          
           ...
           
          
          
           ${html}
           ...            `;

      那這樣就大功告成啦!!!!

      我來(lái)說(shuō)一下在做這個(gè)的時(shí)候遇到的坑

      1.不能使用分離 css 的插件 mini-css-extract-plugin ,因?yàn)榉蛛x css 和把 css 放置到 style 中會(huì)有沖突,引入github大神的一句話

      With isomorphic-style-loader the idea was to always include css into js files but render into dom only critical css and also make this solution universal (works the same on client and server side). If you want to extract css into separate files you probably need to find another way how to generate critical css rather than use isomorphic-style-loader.

      2.很多文章說(shuō)到在 service 端的打包中不需要打包 css,那是因?yàn)樗麄兪褂玫氖莝tyle-loader 的情況,我們?nèi)绻褂?isomorphic-style-loader, 我們也需要把 css 打包一下,因?yàn)槲覀冊(cè)诜?wù)端中畢竟要觸發(fā) withStyle。

      感謝你能夠認(rèn)真閱讀完這篇文章,希望小編分享的“react koa rematch怎么打造一套服務(wù)端渲染架子”這篇文章對(duì)大家有幫助,同時(shí)也希望大家多多支持創(chuàng)新互聯(lián),關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道,更多相關(guān)知識(shí)等著你來(lái)學(xué)習(xí)!


      文章標(biāo)題:reactkoarematch怎么打造一套服務(wù)端渲染架子
      轉(zhuǎn)載源于:http://www.ef60e0e.cn/article/gdcojh.html
      99热在线精品一区二区三区_国产伦精品一区二区三区女破破_亚洲一区二区三区无码_精品国产欧美日韩另类一区
      1. <ul id="0c1fb"></ul>

        <noscript id="0c1fb"><video id="0c1fb"></video></noscript>
        <noscript id="0c1fb"><listing id="0c1fb"><thead id="0c1fb"></thead></listing></noscript>

        山阳县| 金沙县| 宁城县| 荔波县| 陇南市| 峨山| 上思县| 汶上县| 信宜市| 肥城市| 浠水县| 仪征市| 策勒县| 长沙市| 松潘县| 密山市| 忻州市| 太谷县| 东光县| 周口市| 万山特区| 新密市| 桑植县| 左权县| 潜山县| 佛冈县| 伊宁县| 曲麻莱县| 丰镇市| 陈巴尔虎旗| 临湘市| 固始县| 潮安县| 南皮县| 铁力市| 锦州市| 巨野县| 乌拉特中旗| 东乌珠穆沁旗| 双牌县| 颍上县|