揭開react-loadable的神祕面紗

react-loadable是什麼?咱們首先看看官方說明javascript

A higher order component for loading components with dynamic imports.java

經過hoc給組件提供動態加載功能,在實際業務開發中,常常遇到性能優化的問題,其中有一個優化的點,就是經過動態加載組件來減小首屏的size,在react官方的lazy 和suspense出來以前,相信這是react最流行的動態加載的庫,包括如今也是,最近在研究next.js的過程當中,發現next的dynamic的實現也是基於這個庫,廢話很少說,接下來就讓咱們揭開react-loadable的神祕面紗react

如何使用

這點官方文檔寫的很清楚,以下所示,Loadable的loader屬性中使用import來動態加載組件,同時其中的loading屬性是咱們可配置的loading的componentwebpack

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了,接下來咱們就能夠着手解決如何在晚上登陸太陽了,哈哈哈web

首先咱們先研究入口,

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方法的定義promise

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,就是這個簡單

相關文章
相關標籤/搜索