react代碼拆分之react loadable源碼淺析

在作我的網站的js拆分打包時,最終的解決方案是看着網上的教程手寫了Bundle高階組件來動態加載須要的組件。對於它的運用也僅僅是把路由拆開,訪問不一樣的頂級路由進行動態加載,並無對其原理進行深刻的理解。直到看到了React 的加載 loading 庫——react-loadablereact

react-loadable是什麼?

Loadable提倡基於組件分割代碼。 webpack

image

route-centric code splitting is shit, component-centric splitting is cool as shit.git

網上的翻譯有不少,本文就不過多對readme操做了,簡單的介紹一下就是: Loadable 是一個高階組件(簡單來講,就是把組件做爲輸入的組件。高階函數就是把函數做爲輸入的函數。在 React 裏,函數和組件有時是一回事),一個能夠構建組件的函數(函數能夠是組件),它能夠很容易的在組件層面分割代碼包。github

react-loadable怎麼用?

下面是github庫裏readme給的例子。web

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

const LoadableComponent = Loadable({
  loader: () => import('./my-component'), // 要按需加載的組件,用了import()函數
  loading: Loading,    // 一個無狀態組件,負責顯示"Loading中"
});

export default class App extends React.Component {
  render() {
    return <LoadableComponent/>;
  }
}
複製代碼

my-loading-component.js

import React from 'react';

export default function Loading(props) {
  if (props.isLoading) {
    if (props.timedOut) {
      return <div>Loader timed out!</div>;
    } else if (props.pastDelay) {
      return <div>Loading...</div>;
    } else {
      return null;
    }
  } else if (props.error) {
    return <div>Error! Component failed to load</div>;
  } else {
    return null;
  }
}
複製代碼

源碼分析

源碼結構

這是loadable的源碼結構 promise

image

最後export出來的是一個Loadable函數,因此咱們從Loadable開始分析。bash

module.exports = Loadable;

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

複製代碼

Loadable接受一個參數opts,再調用了createLoadableComponent函數,傳入了load函數和opts。異步

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參數。咱們先放在這裏,一會在回來看這個loader函數

createLoadableComponent

createLoadableComponent

這個函數是整個庫的畢竟重要的函數了。(由於本文是淺析,因此先只分析主邏輯的函數,別的健壯性支持函數可能會以後再分析)源碼分析

if(!options.loading){...}

options.loading就是上文提到的一個無狀態組件,負責顯示"Loading中",若是不存在,會報錯,須要一個Loading中顯示的組件。

let opts = Object.assign(...,options)
let opts = Object.assign({
        loader: null,
        loading: null,
        delay: 200,
        timeout: null,
        render: render,
        webpack: null,
        modules: null,
    }, options);
複製代碼

就是初始化一下opts的值,賦給未傳入參數初始值,防止接下來的判斷報錯。

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

init以前聲明過一個resinit就是爲res賦值。在init裏調用了loadFn,就是上文說過的load函數

load函數接收一個參數,就是以前() => import('./my-component'),這裏的import()方法,返回一個Promise對象,[[PromiseValue]].default就是待load組件的function。

image

load函數裏初始化了一個state對象,並在import()方法返回的Promise中,對state的屬性賦值,loading表明是否加載完成, loaded爲加載的對象,這裏的state.loaded已是[[PromiseValue]]了。

return class LoadableComponent extends React.Component ...

由於react loadable的描述終究是一個高階組件,若是對高階組件不瞭解的,能夠去看 深刻React高階組件(HOC)這篇文章。 因此createLoadableComponent最後返回的是一個React Component。

一開始的constructor裏,調用了init,將res的幾個屬性,分別賦值給this.state做爲組件初始化。

this.state = {
    error: res.error,
    pastDelay: false,
    timedOut: false,
    loading: res.loading,
    loaded: res.loaded
};
複製代碼

以後在componentWillMount中,作了這些操做

  • 設置了個標記位this._mounted,默認爲true
  • 進行一些判斷,若是res.loading爲true,說明以前的init執行出錯,直接return;
  • 若是opts.delayopts.timeout有值,且爲number屬性的話,就加個定時器。
  • 聲明update函數,若是this._mounted爲false,從新將res的屬性更新到state裏。

render中進行了判斷,

  • 若是this.state.loading或者this.state.error爲true,就是總體狀態是正在加載ing或者出錯了,就用React.createElement生成出loading的過渡組件,
  • 若是this.state.loaded有值了,說明傳入的loader的promise異步操做執行完成,就開始渲染真正的組件,調用opts.render方法.(此時的this.state.loaded就是[[PromiseValue]])
    image
function resolve(obj) {
    return obj && obj.__esModule ? obj.default : obj;
}
function render(loaded, props) {
    return React.createElement(resolve(loaded), props);
}
複製代碼

render的時候進行判斷,當__esModule爲true的時候,標識module爲es模塊,那麼obj默認返回obj.default,那麼obj默認返回obj。以後再調用React.createElement進行渲染。

至於props是什麼?在項目裏打印發現,就是原組件的props。因此render出的,就和正常在代碼中寫的React Component是同樣的。

image
而後,整個loadable就完成了。

總結

這個庫的設計仍是很是巧妙,利用import返回一個promise對象的性質,進行loading的異步操做,有點像圖片懶加載的原理。下面是劃重點系列,

  • import() 能夠返回一個promise對象
  • React.createElement() API
  • Promise對象原理。

本文只是對react loadable這個庫的核心源代碼的分析,還有一些別的代碼沒有分析到,以及一些分析失誤的地方,都歡迎你們交流分享。能夠發郵件給我:uiryzd@163.com

react loadable源碼地址

相關文章
相關標籤/搜索