[React 從零實踐01-后臺] 代碼分割

導(dǎo)航

[react] Hooks

[React 從零實踐01-后臺] 代碼分割
[React 從零實踐02-后臺] 權(quán)限控制
[React 從零實踐03-后臺] 自定義hooks
[React 從零實踐04-后臺] docker-compose 部署react+egg+nginx+mysql
[React 從零實踐05-后臺] Gitlab-CI使用Docker自動化部署

[源碼-webpack01-前置知識] AST抽象語法樹
[源碼-webpack02-前置知識] Tapable
[源碼-webpack03] 手寫webpack - compiler簡單編譯流程
[源碼] Redux React-Redux01
[源碼] axios
[源碼] vuex
[源碼-vue01] data響應(yīng)式 和 初始化渲染
[源碼-vue02] computed 響應(yīng)式 - 初始化箍铭,訪問任内,更新過程
[源碼-vue03] watch 偵聽屬性 - 初始化和更新
[源碼-vue04] Vue.set 和 vm.$set
[源碼-vue05] Vue.extend

[源碼-vue06] Vue.nextTick 和 vm.$nextTick

[部署01] Nginx
[部署02] Docker 部署vue項目
[部署03] gitlab-CI

[深入01] 執(zhí)行上下文
[深入02] 原型鏈
[深入03] 繼承
[深入04] 事件循環(huán)
[深入05] 柯里化 偏函數(shù) 函數(shù)記憶
[深入06] 隱式轉(zhuǎn)換 和 運算符
[深入07] 瀏覽器緩存機制(http緩存機制)
[深入08] 前端安全
[深入09] 深淺拷貝
[深入10] Debounce Throttle
[深入11] 前端路由
[深入12] 前端模塊化
[深入13] 觀察者模式 發(fā)布訂閱模式 雙向數(shù)據(jù)綁定
[深入14] canvas
[深入15] webSocket
[深入16] webpack
[深入17] http 和 https
[深入18] CSS-interview
[深入19] 手寫Promise
[深入20] 手寫函數(shù)
[深入21] 算法 - 查找和排序

前置知識

一些單詞

automatic:自動的
delimiter:分隔符
( automaticNameDelimiter )

lighthouse:燈塔
priority:優(yōu)先級

vendor: 第三方
Suspense:懸念森爽,懸停
fallback:退路

(1) 為什么要做代碼分割

  • ( A文件 ) 分割成 ( B文件,C文件 )
  • 加載一個2MB的文件A,和加載兩個1MB的文件B和C鳖擒,由于存在異步加載并行加載岳颇,所以分割后可能加載速度更快
  • 當(dāng)修改代碼時颅崩,不做代碼分割,只修改一小部分就會重新打包整個文件A吃引,生成新的文件A'筹陵,用戶端就得從新加載整個文件A';做代碼分割后镊尺,如果修改的代碼在B文件朦佩,從新打包只需要打包B文件,同時用戶端也只需要重新加載B文件鹅心,C文件會被緩存
  • 還可以做按需加載吕粗,懶加載纺荧,路由懶加載旭愧,解決白屏等,最終提升性能

(2) 代碼分割的三種角度

  • 拆分成 ( 業(yè)務(wù)代碼-經(jīng)常變動 ) 和 ( 第三方依賴代碼-幾乎不變 )
    • 業(yè)務(wù)代碼會隨著需求迭代等不斷變化宙暇,而第三方依賴包基本不變输枯,所以可以把第三方依賴包單獨拆分打包,比如叫vender.js這個包基本不變占贫,代碼發(fā)布后桃熄,在用戶端不用重新加載,而是瀏覽器會自動緩存
  • 根據(jù)路由進行切割型奥,即路由懶加載
    • 比如進入首頁的路由時瞳收,只需要加載首頁的那部分代碼
    • 首頁有依賴其他模塊碉京,同步引入其實也可以在拆分成粒度更細(xì)的包,動態(tài)引入的可以通過import()函數(shù)做動態(tài)加載拆分
  • 根據(jù)組件進行切割
    • 按路由方式進行代碼切割螟深,當(dāng)A組件包含C組件谐宙,而B組件也包含C組件時,兩個打包后的包界弧,都會分別包含C組件的代碼凡蜻,造成冗余。
    • 按組件方式進行代碼切割垢箕,則能避免上面的問題划栓,但是由此帶來的問題就是包的數(shù)量會急劇增加,需要開發(fā)者自己衡量利弊条获。

(3) import(specifier) 函數(shù)

  • import加載模塊時忠荞,不能做到像require那樣的運行時加載模塊,所以有了 import()函數(shù) 提案帅掘,動態(tài)加載模塊

  • 參數(shù):模塊的路徑

  • 返回值:返回一個 promise 對象

  • 適用場合:

    • 按需加載:在需要時在加載模塊
    • 條件加載:在if語句中做條件加載
    • 動態(tài)模塊路徑:允許模塊路徑動態(tài)生成
  • 注意點:

    • import()返回的是一個promise實例對象钻洒,加載成功后,模塊對象作為.then() 方法的參數(shù)锄开,可以通過 解構(gòu)賦值 獲取輸出接口
    • 如果模塊有 default 輸出接口素标,可以通過參數(shù)直接獲取default接口,即 .then(moudle => module.default)
    • 通過加載多個模塊
    • 當(dāng) Webpack 解析到import()語法時萍悴,會自動進行代碼分割头遭。如果你使用 Create React App,該功能已開箱即用癣诱,你可以立刻使用該特性计维。當(dāng)然也可以自己配置webpack
    • 當(dāng)使用 Babel 時,你要確保 Babel 能夠解析動態(tài) import 語法而不是將其進行轉(zhuǎn)換撕予。對于這一要求你需要 @babel/plugin-syntax-dynamic-import
import(/* webpackChunkName: "AsyncTest" */'../../components/async-test') 
  .then(({ default: AsyncTest }) => {
    ...
  })
  .catch(err => console.log(err))
  

異步加載鲫惶,動態(tài)加載( import() )
- 代碼拆分如何命名包名 
    1. /* webpackChunkName: "AsyncTest" */
    2. 使用插件 @babel/plugin-syntax-dynamic-import 就可以上面的 魔法注釋 寫法
    3. 通過 create-react-app新建的項目中 
            => babel-preset-react-app 依賴=> @babel/preset-env 依賴=> @babel/plugin-syntax-dynamic-import 
            
同步加載( import )
- 代碼拆分如何命名包名
    1. 通過設(shè)置 optimization => splitchunks => cashGroups 來配置包名

(4) webpack => optimization

  • 對于用webpack構(gòu)建的項目

    • 同步方式引入的模塊( import ),做代碼分割需要配置 optimization.splitchunks
    • 異步方式引入的模塊( import() )实抡,不需要做任何配置
  • optimization.splitchunks

    • automaticNameDelimiter
      • 指定拆分出來的包的連接符欠母,來源組名稱 連接符 入口名稱(例如vendors~main.js)
      • 默認(rèn)是 ~
    • maxAsyncRequests
      • 按需加載時最大的并行請求數(shù),默認(rèn)30
    • maxInitialRequests
      • 入口最大并行請求數(shù)吆寨,默認(rèn)30
    • chunks
      • string 或者 function
      • string時赏淌,有效值為 allasyncinitial啄清,all表示同步和異步模塊都進行拆分
      • function時六水,可以有效的指定具體的哪些模塊需要進行拆分
      • chunks 需要配合 cashGroups
    • cacheGroups
      • priority:定義每個組的優(yōu)先級
        • 當(dāng)一個模塊滿足多個組規(guī)則時,該模塊將被打包到 priority 高的文件中
        • number越大優(yōu)先級越高,默認(rèn)組的默認(rèn)值是負(fù)數(shù)掷贾,自定義組的默認(rèn)值是0
      • filename:打包后模塊的名字
      • reuseExistingChunk:boolean
        • 如果在之前的模塊中引入過該模塊A睛榄,并打包了,現(xiàn)在又引入了模塊A想帅,就復(fù)用之前已經(jīng)打包好的A
    • minChunks(maxChunks)
      • 模塊是否進行拆分的最小引用次數(shù)懈费,即至少該模塊被引用多少次才進行拆分
    • minSize(maxSize)
      • 模塊是否進行拆分的最小大小(以字節(jié)為單位)
  • 官網(wǎng)說明

  • SplitChunksPlugin

optimization.splitchunks默認(rèn)配置項如下:


module.exports = {
  //...
  optimization: {
    splitChunks: {
      chunks: 'all', // 對同步引入模塊 和 異步引入模塊都做代碼分割博脑,all async initial
      minSize: 20000, // 當(dāng)引入的模塊大小大于 20KB 時憎乙,對該模塊進行代碼分割
      minRemainingSize: 0,
      maxSize: 0, // 超過值后,對模塊進行二次拆分
      minChunks: 1, // 引入的模塊被引用一次時就進行代碼分割
      maxAsyncRequests: 30, // 最大的按需(異步)加載次數(shù)叉趣,整個項目最多進行30個代碼分割
      maxInitialRequests: 30, // 最大的初始化加載次數(shù)泞边,首頁最多進行30個代碼分割
      automaticNameDelimiter: '~', // 打包后的名字中的連接符,組名+連接符+入口文件名
      enforceSizeThreshold: 50000,
      cacheGroups: {
        defaultVendors: { // 組名稱
          test: /[\\/]node_modules[\\/]/, // 匹配的范圍是 node_modules
          priority: -10 // 優(yōu)先級疗杉,當(dāng)一個模塊滿足多個組規(guī)則時阵谚,該模塊將被打包到 priority 高的文件中
          // filename: 'vender.js' // 指定打包后模塊的名字
        },
        default: { // 引入的模塊,如果不滿足上面的defaultVendors組規(guī)則的模塊烟具,就會進行default組的規(guī)則匹配
          minChunks: 2,
          priority: -20,
          reuseExistingChunk: true, // 之前已經(jīng)打包過該模塊梢什,就直接復(fù)用
        }
      }
    }
  }
};

(5) 錯誤邊界

部分 UI 中的 JavaScript 錯誤不應(yīng)該破壞整個應(yīng)用程序。 為了解決這個問題朝聋,React引入了 “錯誤邊界(Error Boundaries)”



react中的代碼分割實現(xiàn)

(1) React.lazy 和 Suspense 實現(xiàn)代碼分割

    1. React.lazy(() => import()) 參數(shù)是一個函數(shù)嗡午,函數(shù)返回值必須是一個promsie對象,React.lazy 目前僅支持默認(rèn)導(dǎo)出
    1. Suspense組件冀痕,fallback 屬性接受任何在組件加載過程中你想展示的 React 元素荔睹。你可以將 Suspense 組件置于懶加載組件之上的任何位置。你甚至可以用一個 Suspense 組件包裹多個懶加載組件言蛇。
import React, { useState, Suspense } from 'react'
import { Button } from 'antd';


const Home = (props: any) => {
  console.log(props);

  const [AsyncTest, setAsyncTest] = useState<any>()
  const [AsyncTest2, setAsyncTest2] = useState<any>()

  // import()方式代碼分割
  const asyncLoad1 = () => {
    import(/* webpackChunkName: "AsyncTest" */'../../components/async-test')
      .then(({ default: AsyncTest }) => {
        setAsyncTest((element: any) => element = AsyncTest)
      })
      .catch(err => console.log(err))
  }

  // React.lazy() + Suspense 方式代碼分割
  const asyncLoad2 = () => {
    const Test2 = React.lazy(() => import(/* webpackChunkName: "AsyncTest2" */'../../components/async-test2'))
    setAsyncTest2((component: any) => component = Test2)
  }

  return (
    <div>
      <header>home page bigscreen</header>

      <Button onClick={() => {
        asyncLoad1();
        asyncLoad2()
      }}>異步加載</Button>

      {AsyncTest ? AsyncTest() : null}
      {/* {AsyncTest ? <AsyncTest />: null} */}

      <Suspense fallback={<div>Loading...</div>}>
        {AsyncTest2 ? <AsyncTest2 /> : null}
      </Suspense>
    </div>
  )
}

export default Home

(2) 基于路由的代碼分割(React.laze)(Suspense)(react-router-config)

  • 和vue類似
React.lazy   Suspense   react-router-config


routes.js------------------
const Login = lazy(() => import(/* webpackChunkName: 'Login' */'../pages/login'))
const HomeBigScreen = lazy(() => import(/* webpackChunkName: 'HomeBigScreen' */'../pages/home/bigscreen.home'))
const HomeAdmin = lazy(() => import(/* webpackChunkName: 'HomeAdmin' */'../pages/home/admin.home'))
const Layout = lazy(() => import(/* webpackChunkName: 'Layout' */'../pages/layout'))
const routes: RouteModule[] = [
  {
    path: '/login',
    component: Login,
  },
  {
    path: '/',
    component: Layout,
    routes: [ // -------------------------------------------------------- 嵌套路由
      {
        path: '/home-bigscreen',
        exact: true,
        component: HomeBigScreen,
      },
      {
        path: '/home-admin',
        exact: true,
        component: HomeAdmin,
      },
    ]
  }
]


router.js------------------
import { renderRoutes } from 'react-router-config' //--------------------- react-router-config集中式路由解決方案
const Router = () => {
  return (
    <Suspense fallback={<div>loading...</div>}> //------------------------ Suspense包裹lazy僻他,Suspense.fallback
      <Switch>
        {renderRoutes(routes)}
      </Switch>
    </Suspense>

  )
}


layout.js----------------
const render = () => {
    if (systemType === SYSTEMTYPE.ADMIN) {
      return (
        <div className={styles.layoutAdmin}>
          <header className={styles.header}>layout page admin</header>
          {renderRoutes(props.route.routes)} //--------------------------- renderRoutes(props.router.routes)
        </div>
      )
    } else {
      return (
        <div className={styles.layoutBigScreen}>
          {renderRoutes(props.route.routes)}
        </div>
      )
    }
  }

(3) 基于路由的代碼分割(第三方庫 react-loadable)

項目源碼

資料

官網(wǎng)教程: https://www.html.cn/create-react-app/docs/code-splitting/
react中做代碼分片:https://juejin.im/post/6844903953721737224#heading-1

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市腊尚,隨后出現(xiàn)的幾起案子吨拗,更是在濱河造成了極大的恐慌,老刑警劉巖婿斥,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件劝篷,死亡現(xiàn)場離奇詭異,居然都是意外死亡受扳,警方通過查閱死者的電腦和手機携龟,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進店門兔跌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來勘高,“玉大人,你說我怎么就攤上這事』” “怎么了蕊蝗?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長赖舟。 經(jīng)常有香客問我蓬戚,道長,這世上最難降的妖魔是什么宾抓? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任子漩,我火速辦了婚禮,結(jié)果婚禮上石洗,老公的妹妹穿的比我還像新娘幢泼。我一直安慰自己,他們只是感情好讲衫,可當(dāng)我...
    茶點故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布缕棵。 她就那樣靜靜地躺著,像睡著了一般涉兽。 火紅的嫁衣襯著肌膚如雪招驴。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天枷畏,我揣著相機與錄音别厘,去河邊找鬼。 笑死拥诡,一個胖子當(dāng)著我的面吹牛丹允,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播袋倔,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼雕蔽,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了宾娜?” 一聲冷哼從身側(cè)響起批狐,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎前塔,沒想到半個月后嚣艇,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡华弓,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年食零,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片寂屏。...
    茶點故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡贰谣,死狀恐怖娜搂,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情吱抚,我是刑警寧澤百宇,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布,位于F島的核電站秘豹,受9級特大地震影響携御,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜既绕,卻給世界環(huán)境...
    茶點故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一啄刹、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧凄贩,春花似錦鸵膏、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至评肆,卻和暖如春债查,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背瓜挽。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工盹廷, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人久橙。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓俄占,卻偏偏與公主長得像,于是被迫代替她去往敵國和親淆衷。 傳聞我的和親對象是個殘疾皇子缸榄,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,762評論 2 345

推薦閱讀更多精彩內(nèi)容