深入理解代码分割

为什么要代码分割

日常我们会通过 webpack 打包我们的应用,产生一个 bundle.js 文件。随着我们的项目越写越复杂,bundle.js 文件会随之增大。 由于该文件是唯一的,所以不管用户查看哪个页面、使用哪个功能,都必须先下载所有的功能代码。 当 bundle.js 大到一定程度,就会明显影响用户体验。此时,我们就需要 code splitting ,将代码分片,实现按需异步加载,从而优化应用的性能。

核心原理

关键字import(es规范草案)可以像调用函数一样来动态的导入模块。以这种方式调用,将返回一个 promise。
Webpack会自动据此完成代码分片(chunk),不需要任何额外的手动配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import('./math').then(res=>{
console.log(res.default)
});
import('./print').then(res=>{
res.default('from print')
});
/////////////////
dist
├── 0.bundle.js
├── 0.bundle.js.map
├── 1.bundle.js
├── 1.bundle.js.map
├── bundle.js
├── bundle.js.map
└── index.html

以路由为中心进行代码分片

  • 通过高阶组件函数返回一个React.Component
  • 路由匹配成功则立即调用import()并展示组件
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    function AsyncComponent(loadComponent,placeholder='loading……'){
    return class AyncComponent extends React.Component{
    state = {
    Child:null
    }
    async componentDidMount(){
    loadComponent().then(res=>{
    this.setState({
    Child:res.default
    })
    })
    }
    render(){
    const { Child } = this.state;
    return (
    Child ? <Child {...this.props} /> : placeholder
    );
    }
    }
    }

    const App = ()=>{
    return (
    <Router>
    <Route path="/m1" component={AsyncComponent(()=>{
    return import('./pages/m1');
    })} />
    <Route path="/m2" component={AsyncComponent(()=>{
    return import('./pages/m2');
    })} />
    </Router>
    )
    }
    ReactDOM.render(<App />, document.getElementById('root'));

以组件为中心进行代码分片

路由本身并没有什么特别的,它也是组件。如果以组件为中心进行代码分版,会带来额外的好处:

  • 同一个组件中,针对不着急显示的部分,可以延迟其加载。
  • 分割点不再局限于路由,可以任意指定
  • 与路由匹配成功则加载不同,可以自行指定加载时机
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    function DelayComponent(loadComponent,delay=2000,placeholder='loading……'){
    return class AyncComponent extends React.Component{
    state = {
    Child:null
    }
    async componentDidMount(){
    this.timer = setTimeout(() => {
    loadComponent().then(res=>{
    this.setState({
    Child:res.default
    })
    })
    }, delay);
    }

    componentWillUnmount(){
    this.timer && clearTimeout(this.timer);
    }

    render(){
    const { Child } = this.state;
    return (
    Child ? <Child {...this.props} /> : placeholder
    );
    }
    }
    }

    const DelayM1Child = DelayComponent(()=>import('./m1Child'),2000);

    export default class M1 extends React.Component {
    render() {
    return (
    <div>
    <div>m111111</div>
    <DelayM1Child />
    </div>
    )
    }
    }