導(dǎo)航
[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()返回的是一個promise實例對象钻洒,加載成功后,
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時赏淌,有效值為
all
,async
和initial
啄清,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
- priority:定義每個組的優(yōu)先級
-
minChunks(maxChunks)
- 模塊是否進行拆分的最小引用次數(shù)懈费,即至少該模塊被引用多少次才進行拆分
-
minSize(maxSize)
- 模塊是否進行拆分的最小大小(以字節(jié)為單位)
-
automaticNameDelimiter:
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)代碼分割
- React.lazy(() => import()) 參數(shù)是一個函數(shù)嗡午,函數(shù)返回值必須是一個promsie對象,React.lazy 目前僅支持默認(rèn)導(dǎo)出
- 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