React資源性能優(yōu)化

本文將從以下幾個方面描述React的資源優(yōu)化:

1. Code Splitting饥侵;
2. externals&CDN;
3. DllPlugin袁翁;

版本信息

"webpack": "4.29.6",
"react": "16.8.6",
"react-router": "5.0.0",

一炫狱、路由Code Splitting:

  • @loadable/component

One great feature of the web is that we don’t have to make our visitors download the entire app before they can use it. You can think of code splitting as incrementally downloading the app. To accomplish this we’ll use webpack, @babel/plugin-syntax-dynamic-import, and loadable-components.

在react-router@5官方文檔中推薦了"@loadable/component"做Code Splitting态鳖,具體使用如下:

{
  "presets": ["@babel/preset-react"],
  "plugins": ["@babel/plugin-syntax-dynamic-import"]
}

import loadable from "@loadable/component";
import Loading from "./Loading.js";

const LoadableComponent = loadable(() => import("./Dashboard.js"), {
  fallback: <Loading />
});

export default class LoadableDashboard extends React.Component {
  render() {
    return <LoadableComponent />;
  }
}
  • react的React.lazy+ Suspense

react官網(wǎng)推薦了React.lazy方式進行Code Splitting篙议,具體使用如下:

import { lazy, Suspense } from 'react';

const LoadableComponent = lazy(() => import("./Dashboard.js"));

export default class LoadableDashboard extends React.Component {
  render() {
    return (
      <div>
        <Suspense fallback={<div>Loading...</div>}>
            <LoadableComponent />;
        </Suspense>
      </div>
  )
  }
}

React.lazy 接受一個函數(shù)在孝,這個函數(shù)需要動態(tài)調(diào)用 import()诚啃。它必須返回一個 Promise,該 Promise 需要 resolve 一個 defalut export 的 React 組件私沮。

然后應(yīng)在 Suspense 組件中渲染 lazy 組件始赎,如此使得我們可以使用在等待加載 lazy 組件時做優(yōu)雅降級(如 loading 指示器等)。

  • 自定義方式

其實不管是vue還是react,其路由懶加載的實現(xiàn)得益于wepack的異步模塊打包仔燕,webpack會對代碼中異步引入的模塊單獨打包一份造垛,直到真正調(diào)用的時候才去服務(wù)端拿。

const a = () => import('./LoadableComponent')

const a = (r)=>require.ensure([], () => r(require('./LoadableComponent‘)),'chunkname')

以上代碼本質(zhì)是一樣的晰搀,最終返回一個promise對象五辽,其實就是webpack異步模塊打包方法,只有在模塊真正調(diào)用的時候才會加載厕隧。

在vue-router中我們只要在路由配置的component中直接傳入() => import('./LoadableComponent')即可奔脐。而在react的路由中,它路由配置中的component必須傳入react 的component對象吁讨。所以需要對返回promise對象進行處理髓迎。

  • es6+import+async高階組件

import('path/to/module') -> Promise

import(
  /* webpackChunkName: "my-chunk-name" */
  /* webpackMode: "lazy" */
  'module'
);

webpackChunkName:新 chunk 的名稱。從 webpack 2.6.0 開始建丧,[index] and [request] 占位符排龄,分別支持賦予一個遞增的數(shù)字和實際解析的文件名。Adding this comment will cause our separate chunk to be named [my-chunk-name].js instead of [id].js.


webpackMode:從 webpack 2.6.0 開始翎朱,可以指定以不同的模式解析動態(tài)導(dǎo)入橄维。支持以下選項:

"lazy"(默認(rèn)):為每個 import() 導(dǎo)入的模塊,生成一個可延遲加載(lazy-loadable) chunk拴曲。
"lazy-once":生成一個可以滿足所有 import() 調(diào)用的單個可延遲加載(lazy-loadable) chunk争舞。此 chunk 將在第一次 import() 調(diào)用時獲取,隨后的 import() 調(diào)用將使用相同的網(wǎng)絡(luò)響應(yīng)澈灼。注意竞川,這種模式僅在部分動態(tài)語句中有意義店溢,例如 import(`./locales/${language}.json`),其中可能含有多個被請求的模塊路徑委乌。
"eager":不會生成額外的 chunk床牧,所有模塊都被當(dāng)前 chunk 引入,并且沒有額外的網(wǎng)絡(luò)請求遭贸。仍然會返回 Promise戈咳,但是是 resolved 狀態(tài)久窟。和靜態(tài)導(dǎo)入相對比蝇率,在調(diào)用 import()完成之前,該模塊不會被執(zhí)行桦山。
"weak":嘗試加載模塊算利,如果該模塊函數(shù)已經(jīng)以其他方式加載(即册踩,另一個 chunk 導(dǎo)入過此模塊,或包含模塊的腳本被加載)效拭。仍然會返回 Promise暂吉,但是只有在客戶端上已經(jīng)有該 chunk 時才成功解析。如果該模塊不可用缎患,Promise 將會是 rejected 狀態(tài)慕的,并且網(wǎng)絡(luò)請求永遠不會執(zhí)行。當(dāng)需要的 chunks 始終在(嵌入在頁面中的)初始請求中手動提供挤渔,而不是在應(yīng)用程序?qū)Ш皆谧畛鯖]有提供的模塊導(dǎo)入的情況觸發(fā)肮街,這對于通用渲染(SSR)是非常有用的。

上面說到react的路由中要求必須傳入一個react的component對象判导,然而現(xiàn)在返回的是一個延時的promise對象
嫉父,這個時候我們可以考慮在react的生命周期函數(shù)上做文章,而最終又需要返回一個react組件眼刃,所以我們可以考慮使用高階組件绕辖。具體實現(xiàn)方式如下:

//lazyLoad.js
import React from 'react';

export default function lazyLoad(componentfn) {
    class LazyloadComponent extends React.Component {
        constructor(props) {
            super(props);
            this.state = {
                component: null
            }
        }
        async componentWillMount() {
            const { component} = await componentfn();
            this.setState({component})
        }
        render() {
            const C = this.state.component;
            return C ? <C {...this.props}/> : null;
        }
    }
    return LazyloadComponent;
}

//router.js
import lazyLoad from './lazyLoad'
const a = lazyLoad(() => import("./LoadableComponent"))
  • es6+純import(高階函數(shù))

在上面使用了async+await,本質(zhì)上是返回一個promise對象,在promise返回后加載組件擂红,但是用純import也是可以實現(xiàn)這個功能的仪际。

//lazyLoad.js
import React from 'react';

export default function lazyLoad(componentfn) {
    class LazyloadComponent extends React.Component {
        constructor(props) {
            super(props);
            this.state = {
                component: null
            }
        }
        componentWillMount() {
             this.load();
        }
       load(){
         componentfn().then((Com)=>{  //組件加載完成時
                this.setState({
                    component:Com.default?Com.default:null
                });
            });
       }
        render() {
            const C = this.state.component;
            return C ? <C {...this.props}/> : null;
        }
    }
    return LazyloadComponent;
}

  • es6+require.ensure(高階函數(shù))

require.ensure() 是 webpack 特有的,已經(jīng)被 import() 取代昵骤。

require.ensure(
  dependencies: String[],
  callback: function(require),
  errorCallback: function(error),
  chunkName: String
)

require.ensurer雖然已經(jīng)不推薦使用树碱,但是require.ensure作為原始的懶加載方式,還是可以實現(xiàn)的变秦,具體實現(xiàn)如下:

//lazyLoad.js
import React from 'react';

export default function lazyLoad(componentfn) {
    class LazyloadComponent extends React.Component {
        constructor(props) {
            super(props);
            this.state = {
                component: null
            }
        }
        componentWillMount() {
             this.load();
        }
       load(){
         new Promise((resolve,reject)=>{
                require.ensure([], function(require) {//[]依賴項
                    var c = componentfn().default;
                    resolve(c);
                });
          }).then((data)=>{
                this.setState({
                    Com:data
                });
            });
       }
        render() {
            const C = this.state.component;
            return C ? <C {...this.props}/> : null;
        }
    }
    return LazyloadComponent;
}

//router.js
import lazyLoad from './lazyLoad'
const a = lazyLoad(() => require("./LoadableComponent"))

二成榜、externals&CDN:

externals可以防止將某些import的包打包進bundle中,而是在運行時再去外部(script的方式)獲取這些擴展依賴蹦玫,這樣會減少bundle包大小赎婚。

先說使用方法雨饺,基本上分三步:

- 引入cdn library資源
- 配置 webpack externals
- 文件中引用library
//index.html
<head>
<script crossorigin src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>
</head>

// webpack.config.js
module.exports = {
   externals: {
     'react': 'React',// '包名':'全局變量' 
     'react-dom': 'ReactDOM'
   }
}
// 包名react指的是 `import React from 'react'`中的'react'
//全局變量React指的是react暴露出來的全局對象名

//index.js
import React from 'react'

下面引用時webpack官方文檔,對externals對說明:

externals 配置選項提供了「從輸出的 bundle 中排除依賴」的方法惑淳。相反,所創(chuàng)建的 bundle 依賴于那些存在于用戶環(huán)境(consumer's environment)中的依賴饺窿。此功能通常對 library 開發(fā)人員來說是最有用的歧焦,然而也會有各種各樣的應(yīng)用程序用到它。

防止將某些 import 的包(package)打包到 bundle 中肚医,而是在運行時(runtime)再去從外部獲取這些擴展依賴(external dependencies)绢馍。

三、DllPlugin & DllReferencePlugin:

DllPlugin結(jié)合DllRefrencePlugin插件的運用肠套,對將要產(chǎn)出的bundle文件進行拆解打包舰涌,將不需要改動的第三方插件與自己的業(yè)務(wù)代碼進行分開打包,可以很徹底地加快webpack的打包速度你稚,從而在開發(fā)過程中極大地縮減構(gòu)建時間瓷耙。

DllPlugin這個插件是在一個額外的獨立的 webpack 設(shè)置中創(chuàng)建一個只有 dll 的 bundle(dll-only-bundle)。 這個插件會生成一個名為 manifest.json 的文件刁赖,這個文件是用來讓 [DLLReferencePlugin]映射到相關(guān)的依賴上去搁痛。

DllPlugin的作用就是做了兩件小事:根據(jù)entry,生成一份vendor.dll文件和生成一份manifest.json文件

DllReferencePlugin 這個插件是在 webpack 主配置文件中設(shè)置的宇弛, 這個插件把只有 dll 的 bundle(們)(dll-only-bundle(s)) 引用到需要的預(yù)編譯的依賴鸡典。

DllPlugin和DllRefrencePlugin需要配合使用,它們一個負(fù)責(zé)生成dll文件枪芒,一個負(fù)責(zé)使用dll文件彻况,DllPlugin的需要一個單獨的webpack配置文件,這個配置文件告訴webpack-cli應(yīng)該如何打包這個dll舅踪,而DllRefrencePlugin是在主配置文件中使用纽甘。

  • 使用步驟
  1. 配置一份webpack配置文件,用于生成動態(tài)鏈接庫硫朦。

const path = require('path')
const webpack = require('webpack')
const {
  CleanWebpackPlugin
} = require('clean-webpack-plugin')


// dll文件存放的目錄
const dllPath = 'public/vendor'

module.exports = {
  entry: {
    vendor: ['antd','react-redux','redux','redux-thunk','axios']
  },
  output: {
    path: path.join(__dirname, dllPath),
    filename: '[name].dll.js',
    library: '[name]_[hash]'
  },
  plugins: [
    new CleanWebpackPlugin(),
    new webpack.DllPlugin({
      path: path.join(__dirname, dllPath, '[name]-manifest.json'),
      name: '[name]_[hash]',
      context: process.cwd()
    })
  ]
}

  1. 使用動態(tài)鏈接庫贷腕,黃金搭檔DllReferencePlugin
  externals:{
      'react': 'React',// '包名':'全局變量' 
      'react-dom': 'ReactDOM'
    },
  plugins: [
      new webpack.DllReferencePlugin({
        context: process.cwd(),
        manifest: require('./public/vendor/vendor-manifest.json')
    }),
  1. 在html中引用dll文件
<body>
    <div id="app"></div>
    <script src="./public/vendor/vendor.dll.js"></script>
</body>

DllPlugin優(yōu)化,使用于將項目依賴的基礎(chǔ)模塊(第三方模塊)抽離出來咬展,然后打包到一個個單獨的動態(tài)鏈接庫中泽裳。當(dāng)下一次打包時,通過ReferencePlugin破婆,如果打包過程中發(fā)現(xiàn)需要導(dǎo)入的模塊存在于某個動態(tài)鏈接庫中涮总,就不能再次被打包,而是去動態(tài)鏈接庫中g(shù)et到祷舀。

DllPlugin實際上也是屬于公共代碼提取的范疇瀑梗,但與CommonsChunkPlugin不一樣的是烹笔,它不僅僅是把公用代碼提取出來放到一個獨立的文件供不同的頁面來使用,它更重要的一點是:把公用代碼和它的使用者(業(yè)務(wù)代碼)從編譯這一步就分離出來抛丽。

參考文獻

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末谤职,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子亿鲜,更是在濱河造成了極大的恐慌允蜈,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蒿柳,死亡現(xiàn)場離奇詭異饶套,居然都是意外死亡,警方通過查閱死者的電腦和手機垒探,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進店門妓蛮,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人圾叼,你說我怎么就攤上這事蛤克。” “怎么了褐奥?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵咖耘,是天一觀的道長。 經(jīng)常有香客問我撬码,道長儿倒,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任呜笑,我火速辦了婚禮夫否,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘叫胁。我一直安慰自己凰慈,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布驼鹅。 她就那樣靜靜地躺著微谓,像睡著了一般。 火紅的嫁衣襯著肌膚如雪输钩。 梳的紋絲不亂的頭發(fā)上豺型,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天,我揣著相機與錄音买乃,去河邊找鬼姻氨。 笑死,一個胖子當(dāng)著我的面吹牛剪验,可吹牛的內(nèi)容都是我干的肴焊。 我是一名探鬼主播前联,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼娶眷!你這毒婦竟也來了似嗤?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤届宠,失蹤者是張志新(化名)和其女友劉穎双谆,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體席揽,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年谓厘,在試婚紗的時候發(fā)現(xiàn)自己被綠了幌羞。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡竟稳,死狀恐怖属桦,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情他爸,我是刑警寧澤聂宾,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站诊笤,受9級特大地震影響系谐,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜讨跟,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一纪他、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧晾匠,春花似錦茶袒、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至澜共,卻和暖如春向叉,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背咳胃。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工植康, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人展懈。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓销睁,卻偏偏與公主長得像供璧,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子冻记,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,577評論 2 353

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

  • 利用 Webpack 來優(yōu)化 Web 性能屬于加載性能優(yōu)化 的一部分: ? Web Performance Opt...
    一個笑點低的妹紙閱讀 10,588評論 8 8
  • 一.可視化 webpack 輸出文件的大小 添加webpack-bundle-analyzer yarn add ...
    程序人生_小龍閱讀 1,086評論 0 0
  • 1. 前言 隨著前端項目的不斷擴大睡毒,一個原本簡單的網(wǎng)頁應(yīng)用所引用的js文件可能變得越來越龐大。尤其在近期流行的單頁...
    cbw100閱讀 2,183評論 2 8
  • 昨天冗栗,我和我哥玩電動滑板車演顾,這個滑板車加一個板手和一個閘,主要是這個板手隅居,板手只要一扭就飛快地走了钠至,它的速度...
    f8fdc2b43794閱讀 152評論 0 0
  • 當(dāng)遇到字符串中夾雜網(wǎng)址的時候,我們一般的方法都是用正則的方式來挑出網(wǎng)址的部分,然后把它替換成文字,這樣就牽涉到正則...
    Frey丶閱讀 1,503評論 4 7