在作我的網站的js拆分打包時,最終的解決方案是看着網上的教程手寫了Bundle高階組件來動態加載須要的組件。對於它的運用也僅僅是把路由拆開,訪問不一樣的頂級路由進行動態加載,並無對其原理進行深刻的理解。直到看到了React 的加載 loading 庫——react-loadable。react
Loadable提倡基於組件分割代碼。 webpack
route-centric code splitting is shit, component-centric splitting is cool as shit.git
網上的翻譯有不少,本文就不過多對readme操做了,簡單的介紹一下就是: Loadable 是一個高階組件(簡單來講,就是把組件做爲輸入的組件。高階函數就是把函數做爲輸入的函數。在 React 裏,函數和組件有時是一回事),一個能夠構建組件的函數(函數能夠是組件),它能夠很容易的在組件層面分割代碼包。github
下面是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/>;
}
}
複製代碼
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
最後export出來的是一個Loadable
函數,因此咱們從Loadable
開始分析。bash
module.exports = Loadable;
function Loadable(opts) {
return createLoadableComponent(load, opts);
}
複製代碼
Loadable接受一個參數opts
,再調用了createLoadableComponent
函數,傳入了load函數和opts。異步
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
。函數
這個函數是整個庫的畢竟重要的函數了。(由於本文是淺析,因此先只分析主邏輯的函數,別的健壯性支持函數可能會以後再分析)源碼分析
options.loading
就是上文提到的一個無狀態組件,負責顯示"Loading中",若是不存在,會報錯,須要一個Loading中顯示的組件。
let opts = Object.assign({
loader: null,
loading: null,
delay: 200,
timeout: null,
render: render,
webpack: null,
modules: null,
}, options);
複製代碼
就是初始化一下opts的值,賦給未傳入參數初始值,防止接下來的判斷報錯。
function init() {
if (!res) {
res = loadFn(opts.loader);
}
return res.promise;
}
複製代碼
init
以前聲明過一個res
,init
就是爲res
賦值。在init
裏調用了loadFn
,就是上文說過的load函數
。
load函數接收一個參數,就是以前() => import('./my-component')
,這裏的import()
方法,返回一個Promise對象,[[PromiseValue]].default
就是待load組件的function。
load函數裏初始化了一個state對象,並在import()方法返回的Promise中,對state的屬性賦值,loading
表明是否加載完成, loaded
爲加載的對象,這裏的state.loaded
已是[[PromiseValue]]
了。
由於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
,默認爲trueres.loading
爲true,說明以前的init執行出錯,直接return;opts.delay
和opts.timeout
有值,且爲number屬性的話,就加個定時器。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]]
)
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是同樣的。
而後,整個loadable就完成了。這個庫的設計仍是很是巧妙,利用import返回一個promise對象的性質,進行loading的異步操做,有點像圖片懶加載的原理。下面是劃重點系列,
本文只是對react loadable
這個庫的核心源代碼的分析,還有一些別的代碼沒有分析到,以及一些分析失誤的地方,都歡迎你們交流分享。能夠發郵件給我:uiryzd@163.com