React.Suspense和React.lazy代替react-loadable实现路由懒加载

1、react-loadable使用

import { RouteConfig } from 'react-router-config';
import Loadable from 'react-loadable'
import React from 'react';

interface CustomRouteConfig extends RouteConfig {}

const routes: CustomRouteConfig[] = [
  {
    path: '/',
    component: Loadable({
       loader: () => import('../layout'),
       loading: () => React.createElement('span', {}, 'loading'),
    }),
    routes: [
      {
        path: '/keyof',
        component: Loadable({
          loader: () => import('../pages/Keyof'),
          loading: () => React.createElement('span', {}, 'loading')
        })
      },
      {
        path: '/match',
        component: Loadable({
          loader: () => import('../pages/Match'),
          loading: () => React.createElement('span', {}, 'loading')
        })
      },
      {
        path: '/match/:id',
        component: Loadable({
          loader: () => import('../pages/Match'),
          loading: () => React.createElement('span', {}, 'loading')
        })
      },
      {
        path: '/abort',
        component: Loadable({
          loader: () => import('../pages/AbortController'),
          loading: () => React.createElement('span', {}, 'loading')
        })
      },
    ]
  }
]

export default routes

看看Loadable是如何实现懒加载的:

// Loadable
function Loadable(opts) {
  return createLoadableComponent(load, opts);
}

// load函数,用于获取promise对象中的组件
function load(loader) {
  var promise = loader();

  var state = {
    loading: true,
    loaded: null,
    error: null
  };

  state.promise = promise.then(function (loaded) {
    state.loading = false;
    state.loaded = loaded;
    return loaded;
  }).catch(function (err) {
    state.loading = false;
    state.error = err;
    throw err;
  });

  return state;
}

// createLoadableComponent
function createLoadableComponent(loadFn, options) {
  var _class, _temp;

  var res = null;

  function init() {
    if (!res) {
      res = loadFn(opts.loader);
    }
    return res.promise;
  }

  return _temp = _class = function (_React$Component) {
    _inherits(LoadableComponent, _React$Component);

    function LoadableComponent(props) {
      _classCallCheck(this, LoadableComponent);

      var _this = _possibleConstructorReturn(this, _React$Component.call(this, props));

      _this.retry = function () {
        _this.setState({ error: null, loading: true, timedOut: false });
        res = loadFn(opts.loader);
        _this._loadModule();
      };

      init();

      _this.state = {
        error: res.error,
        pastDelay: false,
        timedOut: false,
        loading: res.loading,
        loaded: res.loaded
      };
      return _this;
    }

    LoadableComponent.prototype._clearTimeouts = function _clearTimeouts() {
      clearTimeout(this._delay);
      clearTimeout(this._timeout);
    };

    LoadableComponent.prototype.render = function render() {
      if (this.state.loading || this.state.error) {
        return React.createElement(opts.loading, {
          isLoading: this.state.loading,
          pastDelay: this.state.pastDelay,
          timedOut: this.state.timedOut,
          error: this.state.error,
          retry: this.retry
        });
      } else if (this.state.loaded) {
        return opts.render(this.state.loaded, this.props);
      } else {
        return null;
      }
    };

    return LoadableComponent;
  }(React.Component), _class.contextTypes = {
    loadable: PropTypes.shape({
      report: PropTypes.func.isRequired
    })
  }, _temp;
}

Loadable是一个高阶组件(hoc),在执行Loadable时会执行createLoadableComponent函数,在createLoadableComponent函数中会执行loader函数得到一个promise对象,这个promise对象包含的是导入的组件,最后会返回一个LoadableComponent组件,该组件的render方法会渲染导入的组件,继而完成组件加载。

2、使用React.Suspense、React.lazy

让我们看看React文档对React.lazy的说法:

React.lazy必须通过调用动态的import()加载一个函数,此时会返回一个Promise,并解析(resolve)为一个带有包含React组件的默认导出的模块。

使用React.Suspense可以优化懒加载前的交互,fallback 属性接受任何在组件加载过程中你想展示的 React 元素。你可以将 Suspense 组件置于懒加载组件之上的任何位置。你甚至可以用一个 Suspense 组件包裹多个懒加载组件。React.lazy加载的组件只能在<React.Suspense>组件下使用。

import { RouteConfig } from 'react-router-config';
import Loadable from 'react-loadable'
import React from 'react';
import LoadComp from '../components/Loading';
import Layout from '../layout';

interface CustomRouteConfig extends RouteConfig {}

const routes: CustomRouteConfig[] = [
  {
    path: '/',
    // 如果父级和子级路由都是用lazy、suspense会导致子级路由不会渲染
    // component: LoadComp(React.lazy(() => import('../layout'))),
    component: Layout,
    routes: [
      {
        path: '/keyof',
        component: LoadComp(React.lazy(() => import('../pages/Keyof'))),
      },
      {
        path: '/match/:id',
        component: LoadComp(React.lazy(() => import('../pages/Match'))),
      },
      {
        path: '/abort',
        component: LoadComp(React.lazy(() => import('../pages/AbortController'))),
      },
    ]
  }
]

export default routes
// loadComp高阶组件
import React from 'react';
import Loader from 'react-loader-spinner';

const loadComp = (Com: React.LazyExoticComponent<any>) => {
  return class LoadComp extends React.Component<any, any> {
    render() {
      return (
        <React.Suspense fallback={<Loader type="Puff" color="#00BFFF" height={100} width={100} timeout={3000}/>}>
          <Com />
        </React.Suspense>
      )
    }
  }
}

export default loadComp;

3、替换前后对比

对比react-loadable,React.Suspense还是有一些不足。

  1. 加载组件缺少delay参数,不能解决请求快速完成时的"闪烁"问题(即使加载模块只需要几毫秒的时间, fallback也会被执行,就上述代码来说,也就是 Spinner 会闪烁一下),需要自己封装
  2. 没有内置的加载错误处理方法,需要自己去处理
  3. react-loadable有预加载支持,React.Lazy没有

仍然推荐使用react-loadable来异步加载组件,暂时不用React.Suspense。

参考:https://blog.csdn.net/roaming...

阅读 146

推荐阅读
react填坑之路
用户专栏

1 人关注
27 篇文章
专栏主页