React项目优化之--按需加载asyncComponent&r

2019-06-23  本文已影响0人  nucky_lee

按需加载模块的目的是实现代码分隔,用户打开首页时不用下载全部的代码,打开特定的页面加载特定的代码,提高用户体验。

asyncComponent

用法:

npm i react-async-component

import { asyncComponent } from 'react-async-component';

//import()方法返回的是一个Promise,Promise的返回值只能通过then()来读取,所以你会发现官方的3种使用场景全都是在then()里面操作。
const AsyncHome = asyncComponent(() => import("./containers/Home"));
<Route path="/" exact component={AsyncHome} />

内部原理

// 异步组件 asyncComponent.jsx

import React, { Component } from 'react';
 
const asyncComponent = (importComponent) => {
  return class extends Component {
    constructor() {
      super();
      this.state = {
        component: null
      }
    }
    componentDidMount() {
      importComponent()
        .then(cmp => {
          this.setState({ component: cmp.default });
        });
    }
    render() {
      const C = this.state.component;
      return C ? <C {...this.props} /> : null;
    }
  }
};
 
export default asyncComponent;

react-loadable--一个动态导入加载组件的高阶组件

在开发react单页面应用时,我们会遇到一个问题,那就是打包后的js文件特别巨大,首屏加载会特别缓慢。这个时候我们应该将代码进行分割,按需加载,将js 拆分成若干个chunk.js,用到就加载,react-loadable就可以很好地解决这个问题。

react-loadable是以组件级别来分割代码的,这意味着,我们不仅可以根据路由按需加载,还可以根据组件按需加载,使用方式和路由分割一样,只用修改组件的引入方式即可。

下面实战使用:

//  package.json配置
devDependencies: {
...,
"@babel/plugin-syntax-dynamic-import": "^7.2.0"
"react-loadable": "^5.5.0",
...
}

// in .babelrc
{
  ...,
  "plugins": [
        "@babel/plugin-syntax-dynamic-import"
    ]
  ...
}

// in webpack.config.js
...
output: {
    path: path.resolve(__dirname, 'public'),
    filename: 'js/[name].bundle.js',
    chunkFilename: 'chunks/[name].[chunkhash:8].chunk.js'
  },
import App from './App';
import Loadable from 'react-loadable';
import { BrowserRouter as Router, Route, Link, Switch } from 'react-router-dom';

//通用的过场组件
const MyLoadingComponent = ({ isLoading, error }) => {
    // Handle the loading state
    if (isLoading) {
        return <div>Loading...</div>;
    }
    // Handle the error state
    else if (error) {
        return <div>Sorry, there was a problem loading the page.</div>;
    }
    else {
        return null;
    }
};

const AsyncHome = Loadable({
    loader: () => import('./App'),
    loading: MyLoadingComponent
});
//AsyncHome为按需加载组件
ReactDOM.render(
    <Router>
        <Switch>
            <Route exact path="/" component={AsyncHome} />
        </Switch>
    </Router>, 
    document.getElementById('root')
);

//App为正常加载组件
ReactDOM.render(
    <Router>
        <Switch>
            <Route exact path="/" component={App} />
        </Switch>
    </Router>, 
    document.getElementById('root')
);

看一下浏览器执行之后的区别:


20190623164517.jpg
结论:

0.chunk.js和1.chunk.js文件大小明显小了很多,因为只加载首页所需的依赖,所以体积会小很多,而且这个差距会随着项目的增大而变大。看代码,可以知道,工作原理其实就是在页面组件上有包了一成高级组件来代替原来的页面组件。

封装为util

但是如果项目有100个页面,那laodable.js就需要写100遍,这样就感觉有点冗余了,所以这个我们可以封装一下.

我们建一个util
src/util/loadable.js

import React from 'react';
import Loadable from 'react-loadable';

//通用的过场组件
const MyLoadingComponent = ({ isLoading, error }) => {
    // Handle the loading state
    if (isLoading) {
        return <div>Loading...</div>;
    }
    // Handle the error state
    else if (error) {
        return <div>Sorry, there was a problem loading the page.</div>;
    }
    else {
        return null;
    }
};

//过场组件默认采用通用的,若传入了loading,则采用传入的过场组件
export default (loader,loading = MyLoadingComponent)=>{
    return Loadable({
        loader,
        loading
    });
}

router里面调用方式:

import React from 'react'
import { BrowserRouter, Route } from 'react-router-dom'
import loadable from '../util/loadable'

const Home = loadable(()=>import('@pages/home'))

const Routes = () => (
    <BrowserRouter>
        <Route path="/home" component={Home}/>
    </BrowserRouter>
);

export default Routes

封装之后,laodable只需写一次,改变的只是组件的引入方式,这样一来就方便多了,react-loadable是以组件级别来分割代码的,这意味着,我们不仅可以根据路由按需加载,还可以根据组件按需加载,使用方式和路由分割一样,只用修改组件的引入方式即可。

更多的使用场景推荐访问:React Loadable -- https://www.jianshu.com/p/462bb9d1c982

react-loadable源码

import React from "react";

export default function Loadable(
  loader: () => Promise<React.Component>,
  LoadingComponent: React.Component,
  ErrorComponent?: React.Component | null,
  delay?: number = 200
) {
  // 有时组件加载很快(<200ms),loading 屏只在屏幕上一闪而过。

  // 一些用户研究已证实这会导致用户花更长的时间接受内容。如果不展示任何 loading 内容,用户会接受得更快, 所以有了delay参数。

  let prevLoadedComponent = null;

  return class Loadable extends React.Component {
    state = {
      isLoading: false,
      error: null,
      Component: prevLoadedComponent
    };

    componentWillMount() {
      if (!this.state.Component) {
        this.loadComponent();
      }
    }

    loadComponent() {
      // Loading占位
      this._timeoutId = setTimeout(() => {
        this._timeoutId = null;
        this.setState({ isLoading: true });
      }, this.props.delay);

      // 进行加载
      loader()
        .then(Component => {
          this.clearTimeout();
          prevLoadedComponent = Component;
          this.setState({
            isLoading: false,
            Component
          });
        })
        .catch(error => {
          this.clearTimeout();
          this.setState({
            isLoading: false,
            error
          });
        });
    }

    clearTimeout() {
      if (this._timeoutId) {
        clearTimeout(this._timeoutId);
      }
    }

    render() {
      let { error, isLoading, Component } = this.state;

      // 根据情况渲染Loading 所需组件 以及 错误组件
      if (error && ErrorComponent) {
        return <ErrorComponent error={error} />;
      } else if (isLoading) {
        return <LoadingComponent />;
      } else if (Component) {
        return <Component {...this.props} />;
      }
      return null;
    }
  };
}
上一篇下一篇

猜你喜欢

热点阅读