阅读 577

揭开react-loadable的神秘面纱

react-loadable是什么?我们首先看看官方说明

A higher order component for loading components with dynamic imports.

通过hoc给组件提供动态加载功能,在实际业务开发中,经常遇到性能优化的问题,其中有一个优化的点,就是通过动态加载组件来减少首屏的size,在react官方的lazy 和suspense出来之前,相信这是react最流行的动态加载的库,包括现在也是,最近在研究next.js的过程中,发现next的dynamic的实现也是基于这个库,废话不多说,接下来就让我们揭开react-loadable的神秘面纱

如何使用

这点官方文档写的很清楚,如下所示,Loadable的loader属性中使用import来动态加载组件,同时其中的loading属性是我们可配置的loading的component

import Loadable from 'react-loadable';
import Loading from './my-loading-component';

const LoadableComponent = Loadable({
  loader: () => import('./my-component'),
  loading: Loading,
});

export default class App extends React.Component {
  render() {
    return <LoadableComponent/>;
  }
}
复制代码

源码实现

对,就是这么快,上面已经讲完1+1=2了,接下来我们就可以着手解决如何在晚上登录太阳了,哈哈哈

首先我们先研究入口,

function Loadable(opts) {
  return createLoadableComponent(load, opts);
}
复制代码

如上所示,默认暴露出来的Loadable方法底层主要依赖了createLoadableComponent 和load方法,那我们就先看看createLoadableComponent方法是干什么滴🤔,我们一段段滴贴代码,

if (!options.loading) {
  throw new Error("react-loadable requires a `loading` component");
}
let opts = Object.assign(
    {
      loader: null,
      loading: null,
      delay: 200,
      timeout: null,
      render: render,
      webpack: null,
      modules: null
    },
    options
  );
复制代码

入口这里基本就是常见的options值进行处理,其中这里要求loading属性一定要传,毕竟这个是动态加载嘛,接下来是init方法的定义

let res = null;

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

其中loadFn方法就是我们在调用createLoadableComponent是传进来的load,load方法我们稍后会解释到,在这里我们可以稍微猜到,opts.loader是我们在示例中传进来的

() => import('./my-component')
复制代码

这个方法会返回一个promise,resolve时的值就是我们加载好的组件啦,接下来就是把init方法塞到ALL_INITIALIZERS和READY_INITIALIZERS两个数组里面,这是为了在服务器渲染时,可以预加载我们的组件,塞到这两个数组的组件会被promise.all来执行,如何使用,可以看官方的demo,这里不细展开

ALL_INITIALIZERS.push(init);

if (typeof opts.webpack === "function") {
    READY_INITIALIZERS.push(() => {
      if (isWebpackReady(opts.webpack)) {
        return init();
      }
    });
}
复制代码

在完成这些时候,会返回一个class LoadableComponent extends React.Component 组件,我们先看看这个组件的构造

constructor(props) {
  super(props);
  init();

  this.state = {
    error: res.error,
    pastDelay: false,
    timedOut: false,
    loading: res.loading,
    loaded: res.loaded
  };
}
复制代码

在这里会调用init方法,以及初始化state,init上面我们讲到了会调用load方法,那么我们先跳到load方法看看

function load(loader) {
  let promise = loader();

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

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

  return state;
}
复制代码

load中首先调用loader方法,这个方法就是

() => import('./my-component')
复制代码

然后在then方法中把加载回来的组件赋值给内部的state的loaded,同时把loading置为false,最后把state返回去,state.promise的就是组件加载的promise,只要这个promise被resolve了,那么就是组件加载完毕了,OK,load讲解完毕,我们返回到我们的LoadableComponent组件继续讲解

在componentWillMount生命周期中,首先会通过res判断组件是否正在加载,res是init方法执行后返回的state,如果组件不是正在加载,那么直接return,否则设置组件加载delay和timeout的定时器,接下来到重点了,对res.promise添加then来添加监听组件是否加载完毕,当组件加载完毕的话,会执行update方法

let update = () => {
    if (!this._mounted) {
      return;
    }

    this.setState({
      error: res.error,
      loaded: res.loaded,
      loading: res.loading
    });

    this._clearTimeouts();
  };
复制代码

update方法中会更新state的状态,其中loaded是异步加载回来的组件,最后我们看看render方法

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;
  }
}
复制代码

组件还没加载回来时loading为true,则会加载loading组件,当组件加载完毕时,此时this.state.loaded 为true,这时就会渲染我们异步加载回来的组件,至此,react-loadable分析完毕

总结

通过一步步分析下来,我们可以发现原理很简单,在组件没加载回来之前,先渲染loading组件,当组件加载完毕,通过改变state的值重新渲染,从而加载我们原来想要的组件,emmmm,就是这个简单

关注下面的标签,发现更多相似文章
评论