yuxino / blog

🎬 Life's a Movie
17 stars 2 forks source link

React异步加载组件指北 #76

Closed yuxino closed 3 years ago

yuxino commented 6 years ago

在日常开发中我们都会碰见需要异步加载组件的情况,就比如说一个后台项目。拥有各种权限的分配,部分成员看得见一些表图页面,而一部分人看不见。我们知道表图库比如Echart之累的都满大的,如果我们直接加载进来全部打包到一个bundle的话,整个bundle的体积会比较大。并且除此之外很多页面组件用不上,也许用户一直都不会访问到,所以也没有理由加载,浪费带宽和资源。

这里说的异步加载都是在围绕着router来说,因为router一般都会做这个处理。react开发者的话会选择react-router作为处理控制路由的库。我们先来看看两种不同的场景。使用了异步加载和没有使用异步加载。以我的博客dura来举例子。

没有异步加载组件的情况

我们来看看没有异步载的情况。全部都打包在一个bundle里面的写法。

import React, { Component } from 'react'
import './css/App.css'
import { Route, Switch } from 'react-router'

import HomePage from 'page/HomePage'
import AboutPage from 'page/AboutPage'
import ArchivesPage from 'page/ArchivesPage'
import ClosedPage from 'page/ClosedPage'
import LabelPage from 'page/LablePage'
import LabelsPage from 'page/LablesPage'
import TimeLinePage from 'page/TimelinePage'
import NotFoundPage from 'page/NotFoundPage'

class App extends Component {
  render () {
    return (
      <Switch>
        <Route exact path="/" component={HomePage}  />
        <Route exact path="/about" component={AboutPage}  />
        <Route exact path="/archives" component={ArchivesPage}  />
        <Route exact path="/closed" component={ClosedPage}  />
        <Route exact path="/labels" component={LabelsPage}  />
        <Route exact path="/label/:number" component={LabelPage}  />
        <Route exact path="/timeline" component={TimeLinePage} />
        <Route component={NotFoundPage} />
      </Switch>
    )
  }
}

export default App;

NetWorkd加载的情况

全部打包的情况

我们发现所有东西都打包在了一起,而不是针对页面进行打包,这样会加载无用资源。此时的bundle会是700kb,对我这个项目来说。

使用异步加载

我们来看看异步加载的情况。

我们可以利用webpack 2.0+的特性import(...)语法来做异步加载,这样webpack会自动进行代码的chunk。只有在需要用到的时候才会引用。利用这一特性我们创建一个AsyncComponet的HOC组件帮助我们进行异步加载。

AsyncComponet.js

import React, { Component } from 'react';

export default function asyncComponent(importComponent) {
  class AsyncComponent extends Component {
    constructor(props) {
      super(props);
      this.state = {
        component: null,
      };
    }
    async componentDidMount() {
      const { default: component } = await importComponent();
      this.setState({
        component: component
      });
    }
    render() {
      const C = this.state.component;
      return C
        ? <C {...this.props} />
        : null;
    }
  }
  return AsyncComponent;
}

App.js

import React, { Component } from 'react'
import './css/App.css'
import { Route, Switch } from 'react-router'

// using webpack import syntax up performance
import AsyncComponent from 'hoc/AsyncComponent'
const HomePage = AsyncComponent(() => import('page/HomePage'))
const AboutPage = AsyncComponent(() => import('page/AboutPage'))
const ArchivesPage = AsyncComponent(() => import('page/ArchivesPage'))
const ClosedPage = AsyncComponent(() => import('page/ClosedPage'))
const LabelPage = AsyncComponent(() => import('page/LablePage'))
const LabelsPage = AsyncComponent(() => import('page/LablesPage'))
const TimeLinePage = AsyncComponent(() => import('page/TimelinePage'))
const NotFoundPage = AsyncComponent(() => import('page/NotFoundPage'))

class App extends Component {
  render () {
    return (
      <Switch>
        <Route exact path="/" component={HomePage}  />
        <Route exact path="/about" component={AboutPage}  />
        <Route exact path="/archives" component={ArchivesPage}  />
        <Route exact path="/closed" component={ClosedPage}  />
        <Route exact path="/labels" component={LabelsPage}  />
        <Route exact path="/label/:number" component={LabelPage}  />
        <Route exact path="/timeline" component={TimeLinePage} />
        <Route component={NotFoundPage} />
      </Switch>
    )
  }
}

export default App;

NetWorkd加载的情况

chunk

我们现在再来看bundle的大小,我们会发现bundle缩小到了400kb左右,虽然这个小项目不是太明显也就是缩少了大概百分之40这样子。但是对于大型项目来说,这个倍率会网上递增。

除此之外在我们访问不同的页面的时候,才会对应的chunk

总结

AsyncCompoent利用了webpack懒加载/code spliting特性达到了异步加载的目的。但是AsyncComponet并不完整,存在着很多的问题,比如说加载失败的问题,这里就没有处理,延迟加载,服务端处理,细节上的也没有,比如加载组件过程中的占位(placeholder)组件。

一个更好的解决方案会是使用react-loadable组件做这个事情。不过这篇不做介绍,放下一次吧(可能)。