React性能優化之代碼分割

代碼分割從兩方面來說:React Lazy 和 webpack兩方面html

每次單頁應用打包出的文件又臭又長,是最蛋疼的事,瀏覽器刷個半天才出來網頁讓人巴不得找個地洞鑽進去,這種事很尷尬,讓人懷疑你這前端不專業前端

因此正題來了,這篇文章主要講解node

  • 如何分模塊拆分代碼
  • 如何提取公共庫,避免重複打包(兩個模塊都用到jQuery,如何只打包進一個chunk)
  • 如何打包須要的代碼(好比一個ui庫,你只用到一個Button,那麼只打包Button)
  • React組件如何實現懶加載

先講 React Lazy

先上個最簡單的demo,App.js和Child.jsreact

// App.js

import React from 'react';
import Child from './Child';

function App() {
	return (
		<div className="App">
			<Child />
		</div>
	);
}

export default App;
複製代碼
// Child.js

import React from 'react';

class Child extends React.Component{
    render(){
        return <div class="childClass">我是子組件啊</div>
    }
}

export default Child;
複製代碼

就這個目錄結構,而後執行打包,打包以後以下:jquery

能夠看到Child組件被打包到了main.chunk.js裏面
那麼Child是否能夠作懶加載呢?

查找React文檔,能夠找到code split,那麼用React lazy來對Child組件作一下懶加載,仍是原來的組件App和Childwebpack

// App.js

import React, { lazy, Suspense } from 'react';

const Child = lazy(() => import('./Child'))

function App() {
	return (
		<div className="App">
			<Suspense fallback={<div>Loading...</div>}>
				<Child />
			</Suspense>
		</div>
	);
}

export default App;
複製代碼
// Child.js

import React from 'react';

class Child extends React.Component{
    render(){
        return <div class="childClass">我是子組件啊</div>
    }
}

export default Child;
複製代碼

其實api來講很簡單,就兩句代碼,這就實現了懶加載es6

import React, { lazy, Suspense } from 'react';

const Child = lazy(() => import('./Child'))
複製代碼

但是源碼裏能夠注意到還有一個Suspense,並且不用Suspense的話,瀏覽器會給出一個提示:web

從錯誤提示來看,lazy非要搭配Suspense使用了。那麼Suspense是何方神聖呢?

能夠試想一下,既然是懶加載,那麼當Child還未加載完成以前,這個視圖怎麼辦?api

bingo!Suspense就是爲了處理這個的,讓視圖更友好,爲懶加載組件作優雅降級,它叫加載指示器
好的,那已經加上lazy了,執行打包吧瀏覽器

再刷新下頁面,發現Child組件被打包到了2.chunk.js裏面,說明已經運行成功了。並且在Child加載成功以前,有個loading在提示。good!效果還不錯

但是問題來了。

閱讀React lazy的官方文檔。發現lazy傳入一個函數,並且此函數須要返回一個Thenable,那麼當沒有返回Thenable的時候會怎麼辦呢?

發現瀏覽器掛了。。。這只是試下異常狀況,因此最好仍是按照React的官方文檔去操做吧

再從webpack談下

webpack的代碼分離有三種方法:

  • 多個entry
  • 插件來防止重複:SplitChunksPlugin
  • 動態導入
先講下最高端的,動態導入(以es6 import爲🌰)

此章節講解如何一個ui庫,解構導入let { Button } = ui;只導入Button
這也是老生常談的antd的import { Button } from 'antd',只導入Button問題

先上個反面教材吧

// index.js

import { mainAdd } from './main';
console.log(mainAdd(1, 2));
複製代碼
// main.js

const mainAdd = (a, b) => {
    return a + b;
}

const mainMultiple = (a, b) => {
    return a * b;
}

export {
    mainAdd,
    mainMultiple
}
複製代碼

webpack.config.js的配置entry就只配置index.js,來webpack打包一把

血崩啊,明明只是import了mainAdd,mainMultiple咋還打包進去了

好的,反面教材講完了,如今教你們如何修改,先修改一下文件目錄

// webpack.config.js

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
	mode: 'development',
	entry: {
		index: './src/index.js'
	},
	output: {
		filename: '[name].bundle.js',
		path: path.resolve(__dirname, 'dist')
	},
	plugins: [
		new HtmlWebpackPlugin()
	],
	module: {
		rules: [
			{
                test: /\.js$/,
				loaders: 'babel-loader',
				include: path.join(__dirname, 'src'),
				options: {
					plugins: [
						["import", { "libraryName": "main", "libraryDirectory": "../../src/main"}],
					]
				},
			}
		]
	}
};
複製代碼
//index.js

import { mainAdd } from 'main';
console.log(mainAdd(1, 2));
複製代碼
// main目錄下的main-add.js

const mainAdd = (a, b) => {
    return a + b;
}
export default mainAdd;
複製代碼
// main目錄下的main-multiple.js

const mainMultiple = (a, b) => {
    return a * b;
}
export default mainMultiple;
複製代碼

好的,再來執行一次打包,發現只打包進了mainAdd,而mainMultiple沒有打包進去。

總結一下,主要用到一個babel的知識點

  • 須要用到babel-plugin-import插件。顧名思義,就是個動態import的plugin,而後配置babel-loader的options
options: {
	plugins: [
		["import", { "libraryName": "main", "libraryDirectory": "../../src/main"}],
	]
},
複製代碼

libraryName指代的是import { mainAdd } from 'main'這裏的main -> 庫名,libraryDirectory是main下面的哪一個文件夾。好比antd/lib/button,這裏libraryDirectory配置的是lib,這裏默認是指向node_modules下的。我是放在src/main目錄下,因此這裏配置爲"../../src/main"

webpack Entry多入口

這個就很少說了,你們用的都比較多

webpack 公共庫拆分打包

仍是老習慣,先來個反面教材

// index.js

import _ from 'lodash';
import { each } from 'jquery';

console.log(
    _.join(['index', 'module', 'loaded!'], ' ')
);
複製代碼
// main.js

import { each } from 'jquery';

let arr = ['zhangsan', 'lisi'];

each(arr, (idx, item) => {
    console.log(item);
})
複製代碼
// webpack.config.js

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
	mode: 'development',
	entry: {
		index: './src/index.js',
		another: './src/main.js'
	},
	output: {
		filename: '[name].bundle.js',
		path: path.resolve(__dirname, 'dist')
	},
	plugins: [
		new HtmlWebpackPlugin()
	],
	module: {
		rules: [
			{
                test: /\.js$/,
				loaders: ['babel-loader'],
				include: path.join(__dirname, 'src')
			}
		]
	}
};
複製代碼

兩個入口,打包一把

又血崩了,發現index.bundle.js打包進了jquery,another.bundle.js也打包進了jquery,能夠打開兩個bundle.js進去查找下jquery看看是否打包進去。

那麼理想狀態下,應該是一份jquery,一份index,一份main,三份代碼。用官方文檔SplitChunksPlugin來試一把,index.js main.js webpack.config.js代碼幾乎不變。

// webpack.config.js中加上一項配置

optimization: {
	splitChunks: {
		chunks: 'all'
	}
}
複製代碼

再打包一下

發現公共庫被提取出來了,index和another兩個文件只包含的業務代碼

看一下chunk的裏面的代碼,發現只有verdors~other~index.bundle.js裏面有require("jquery"),而vendors~index.bundle.js裏面是直接用jquery的,並無引入jquery源碼

說明咱們的效果已經達到了。

至此四個功能已經講完了,本身寫個demo來演練下吧,有問題能夠加我微信demo0808

  • React的lazy懶加載
  • webpack的entry多入口
  • SplitChunksPlugin提取公共庫代碼
  • babel-plugin-import動態導入antd下的Button
相關文章
相關標籤/搜索