欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2024-03-14 20:42:35
...

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。