react-loadable原理淺析

始悔不悟發表於2019-03-04

react-loadable

最近在學習react,之前做的一個專案首屏載入速度很慢,便蒐集了一些優化方法,react-loadable這個庫是我在研究路由元件按需載入的過程中發現的,其實react-router v4官方也有code splitting的相關實踐,但是在現在的webpack 3.0版本下已經不行了,因為需要使用以下相關語法

import loadSomething from 'bundle-loader?lazy!./Something'
複製程式碼

有興趣的同學可以自行研究。

react-router-v4:code-splitting

後面就發現了react-loadable這個庫可以幫助我們按需載入,其實和react-router-v4官方實現的原理差不太多,基本使用方法如下:

第一步:先準備一個Loding元件,這個是官方的,你自己寫一個更好:

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;
    }
};
複製程式碼

第二步:引入react-loadable

import Loadable from 'react-loadable';

const AsyncHome = Loadable({
    loader: () => import('../containers/Home'),
    loading: MyLoadingComponent
});
複製程式碼

第三步:替換我們原本的元件

 <Route path="/" exact component={AsyncHome} />
複製程式碼

這樣,你就會發現只有路由匹配的時候,元件才被import進來,達到了code splitting的效果,也就是我們常說的按需載入,程式碼分塊,而不是一開始就將全部元件載入。

chunk

可以觀察到,點選不同的路由都會載入一個chunk.js,這就是我們所分的塊。

核心:import()

不要把 import關鍵字和import()方法弄混了,該方法是為了進行動態載入才被引入的。

import關鍵字的引入是靜態編譯且存在提升的,這就對我們按需載入產生了阻力(畫外音:require是可以動態載入的),所以才有了import(),而react-loadable便是利用了import()來進行動態載入。

阮一峰:Module的載入實現

tc39/proposal-dynamic-import

而且這個方法不能傳變數,只能使用字串和字串模板,原本想將那一堆生成元件的程式碼進行抽象,結果死活不行,才發現坑在這裡。

Loadable

react-loadable有5k star,內部機制已經十分完善了,看現在的原始碼我肯定看不懂,於是誤打誤撞地看了其initial commit的原始碼。

我們上面對Loadable函式的用法是這樣的:

const AsyncHome = Loadable({
    loader: () => import('../containers/Home'),
    loading: MyLoadingComponent
});
複製程式碼

接收一個配置物件為引數,第一個屬性名為loader,是一個方法,用於動態載入我們所需要的模組,第二個引數就是我們的Loading元件咯,在動態載入還未完成的過程中會有該元件佔位。

{
  loader: () => import('../containers/Home'),
  loading: MyLoadingComponent
}
複製程式碼

這個方法的返回值是一個react component,我們Route元件和url香匹配時,載入的就是這個component,該component通過loader方法進行非同步載入元件以及錯誤處理。其實就這麼多,也有點高階元件的意思。

然後來看看原始碼吧(原始碼引數部分使用了ts進行型別檢查)。

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;
    }
  };
}
複製程式碼

安利一下我正在練習的專案react-music

參考資料:

React Loadable 介紹

webpack v3 結合 react-router v4 做 dynamic import — 按需載入(懶載入)

阮一峰:Module的載入實現

tc39/proposal-dynamic-import

在react-router4中進行程式碼拆分(基於webpack)

react-router-v4:code-splitting

相關文章