在 React 中使用Code Splitting實(shí)現(xiàn)按需加載

一佛致、明確概念

◆ Code Splitting(代碼分離)

Code Splitting 是 webpack 作為打包工具的核心特征派撕。它通過分離點(diǎn)(邏輯斷點(diǎn))的形式將代碼拆分成Chunk,在分離點(diǎn)中依賴的模塊會被打包到一起,實(shí)現(xiàn)異步加載。一個(gè)分離點(diǎn)會產(chǎn)生一個(gè)打包文件疲扎,從而減小資源體積,縮短加載時(shí)間捷雕。webpack 中文指南#代碼分離 對此概念進(jìn)行了詳細(xì)的介紹椒丧,指出三種最常用的代碼分離方法:

  • 入口起點(diǎn):使用 entry 配置手動(dòng)地分離代碼。
  • 防止重復(fù):使用 CommonsChunkPlugin 去重和分離 chunk救巷,用于分離第三方庫壶熏。
  • 動(dòng)態(tài)導(dǎo)入:通過模塊的內(nèi)聯(lián)函數(shù)調(diào)用來分離代碼。

另外浦译,一些有助于代碼分離的插件和 loaders:

  • ExtractTextPlugin: 用于將 CSS 從主應(yīng)用程序中分離棒假。
  • bundle-loader: 用于分離代碼和延遲加載生成的 bundle。
  • promise-loader: 類似于 bundle-loader精盅,但是使用的是 promises帽哑。
◆ 按需加載(或懶加載)

Code Splitting 僅僅是分離了代碼,仍然會提前加載完所有模塊叹俏。它雖然減小了應(yīng)用的總體體積祝拯,在控制資源加載優(yōu)先級的情況下,可以加快應(yīng)用的加載速度(首頁渲染)她肯,但是也同時(shí)也增加了 http 請求數(shù),并非真正意義上的按需加載鹰贵。不過晴氨,Code Splitting 卻為按需加載提供了很好的途徑。

要實(shí)現(xiàn)真正意義上的按需加載(或懶加載)碉输,就需要結(jié)合用戶的交互:當(dāng)只有在用戶訪問某個(gè)功能的時(shí)候才加載該部分的代碼籽前,沒有用到的代碼塊可能永遠(yuǎn)都不會被加載,這樣就可以更好地提升性能敷钾。

二枝哄、在 React 中實(shí)現(xiàn)按需加載

一般,在使用 webpack 時(shí)阻荒,我們已經(jīng)進(jìn)行了代碼分離:抽離 css挠锥,抽離第三方庫到 vendor.js 中,而將自己寫的源碼打包到 bundle.js侨赡,如下圖中給出了對webpack.config.js的基礎(chǔ)配置:

在webpack中進(jìn)行部分代碼分離

對于 bundle.js蓖租,還能再通過 Code Splitting 進(jìn)行優(yōu)化粱侣。在 React 中,一個(gè)路由就是一個(gè)組件蓖宦,因而從路由層面進(jìn)行代碼分離是一個(gè)很容易的切入點(diǎn)齐婴。這里,我們使用 webpack 的 bundle loader (Code Splitting的一種方式)和 <Bundle> 組件來實(shí)現(xiàn)在 React 中的按需加載稠茂。

1. 首先柠偶,安裝 bundle-loader
npm i --save bundle-loader
2. 實(shí)現(xiàn) <Bundle> 組件
import React, { Component } from 'react'

class Bundle extends Component {
  state = {
    // short for "module" but that's a keyword in js, so "mod"
    mod: null
  }

  componentWillMount() {
    this.load(this.props)
  }

  componentWillReceiveProps(nextProps) {
    if (nextProps.load !== this.props.load) {
      this.load(nextProps)
    }
  }

  load(props) {
    this.setState({
      mod: null
    })
    props.load((mod) => {
      this.setState({
        // handle both es imports and cjs
        mod: mod.default ? mod.default : mod
      })
    })
  }

  render() {
    return this.state.mod ? this.props.children(this.state.mod) : null
  }
}

export default Bundle

由上述的實(shí)現(xiàn),我們可以看到整個(gè)加載的實(shí)現(xiàn)過程:

  • webpack bundle loader 通過 load 參數(shù)的方式傳遞給 Bundle睬关;
  • 當(dāng) Bundle 加載完或者接收到新的 prop 的時(shí)候诱担,就會調(diào)用 load,然后在回調(diào)函數(shù)中設(shè)置 Bundle 的狀態(tài)值共螺;
  • 最后该肴,渲染模塊。
3. 結(jié)合 bundle-loader<Bundle> 實(shí)現(xiàn)按需加載
// 通過 `bundle-loader` 懶加載方式引入需要按需加載的模塊
import loadSomething from 'bundle-loader?lazy!./Something'

// 范式
<Bundle load={loadSomething}>
  {(mod) => (
    // do something with the module
  )}
</Bundle>

// 例子:如果 module 是個(gè) component
 <Bundle load={loadSomething}>
  {(Comp) => (Comp
    ? <Comp/>
    : <Loading/>
  )}
</Bundle>

三藐不、使用 bundle loader 相比于使用 import() 的優(yōu)勢

先來看看 import() 方式:

// 同步的 synchronous
import React from 'react';
import { tools } from '../js/util';

// 異步的 asynchronous
const AsyncModulePromise = import ('../js/lib/Module);

import() 已經(jīng)進(jìn)入了 stage 3匀哄,它接收動(dòng)態(tài)模塊名,返回的是一個(gè) Promise雏蛮,需要引入 Babel 插件:syntax-dynamic-import涎嚼。

// a.js
const a = () => console.log("hello I'm a!");
export default a;

// b.js
const b = () => console.log("Hello I'm b!");
export default b;

// index.js
import a from './a';
a();  // hello I'm a!

const bPromise = import('./b');
bPromise.then(b => {
    b.default();  // hello I'm b!
})
.catch(e => console.error(e));

兩者都是動(dòng)態(tài)引入的方式,當(dāng)渲染的時(shí)候才加載代碼挑秉。import 動(dòng)態(tài)加載部分實(shí)踐的時(shí)候有報(bào)錯(cuò)法梯,估計(jì)是配置問題,還待研究犀概。立哑。。具體可參考:ECMAScript 6 入門#import姻灶。

在 React 中铛绰,將 import() 的運(yùn)用結(jié)合到了 <Bundle> 組件中,這樣做产喉,有一個(gè)極大的優(yōu)勢:(附上原文捂掰,求大神解釋:Another HUGE benefit of bundle loader is that the second time it calls back synchronously, which prevents flashing the loading screen every time you visit a code-split screen.)

4. 加載效果展示

在按需加載之前:


在按需加載之前

在按需加載之后:


在按需加載之后

可以看到,當(dāng)使用 Code Splitting 之后曾沈,相應(yīng)的模塊只有在需要(如點(diǎn)擊)的時(shí)候才會被加載这嚣。
ps:當(dāng)然這個(gè)例子中,1.bundle.js2.bundle.js 的體積相對于 bundle.js 幾乎可以忽略不計(jì)(因?yàn)檫@個(gè)原因塞俱,我還特意增加了模塊中的代碼量姐帚,才使得兩次 bundle.js 有了小數(shù)點(diǎn)的變化~/笑哭),但是障涯,在大型項(xiàng)目中卧土,按需加載確實(shí)是一個(gè)提升性能很好的優(yōu)化點(diǎn)惫皱。

5. 參考

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末尤莺,一起剝皮案震驚了整個(gè)濱河市旅敷,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌颤霎,老刑警劉巖媳谁,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異友酱,居然都是意外死亡晴音,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進(jìn)店門缔杉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來锤躁,“玉大人,你說我怎么就攤上這事或详∠敌撸” “怎么了?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵霸琴,是天一觀的道長椒振。 經(jīng)常有香客問我,道長梧乘,這世上最難降的妖魔是什么澎迎? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮选调,結(jié)果婚禮上夹供,老公的妹妹穿的比我還像新娘。我一直安慰自己仁堪,他們只是感情好罩引,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著枝笨,像睡著了一般。 火紅的嫁衣襯著肌膚如雪揭蜒。 梳的紋絲不亂的頭發(fā)上横浑,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天,我揣著相機(jī)與錄音屉更,去河邊找鬼徙融。 笑死,一個(gè)胖子當(dāng)著我的面吹牛瑰谜,可吹牛的內(nèi)容都是我干的欺冀。 我是一名探鬼主播树绩,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼隐轩!你這毒婦竟也來了饺饭?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤职车,失蹤者是張志新(化名)和其女友劉穎瘫俊,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體悴灵,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡扛芽,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了积瞒。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片川尖。...
    茶點(diǎn)故事閱讀 39,785評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖茫孔,靈堂內(nèi)的尸體忽然破棺而出叮喳,到底是詐尸還是另有隱情,我是刑警寧澤银酬,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布嘲更,位于F島的核電站,受9級特大地震影響揩瞪,放射性物質(zhì)發(fā)生泄漏赋朦。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一李破、第九天 我趴在偏房一處隱蔽的房頂上張望宠哄。 院中可真熱鬧,春花似錦嗤攻、人聲如沸毛嫉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽承粤。三九已至,卻和暖如春闯团,著一層夾襖步出監(jiān)牢的瞬間辛臊,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工房交, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留彻舰,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像刃唤,于是被迫代替她去往敵國和親隔心。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評論 2 354

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