react-loadable 原始碼解析
簡要的來說, loadable 是一個高階函式, 他同時利用了 react 的渲染 API, webpack 知識點, babel, promise 合併起來的元件
使用
首先我們要知道 react-loadable
他的用法是什麼:
- loader
需要延遲載入的元件, 使用必須要用() => import('xxx')
語法 - loading
loading 元件, props 接受 error, pastDelay , timedOut, retry引數, 可以自定義 - delay
可以新增延遲 - timeout
超時時間 - render
型別為: (loaded, props)=>ReactNode, 可以新增額外的引數注入
多個元件同時載入
其中接受的引數 loader, render, 型別和上述差不太多
Loadable.Map({
loader: {
Bar: () => import('./Bar'),
i18n: () => fetch('./i18n/bar.json').then(res => res.json()),
},
render(loaded, props) {
let Bar = loaded.Bar.default;
let i18n = loaded.i18n;
return <Bar {...props} i18n={i18n}/>;
},
})
預載入
const LoadableBar = Loadable({
loader: () => import('./Bar'),
loading: Loading,
});
觸發:
LoadableBar.preload();
庫裡還涉及到 SSR 相關知識點, 這裡就不涉及了
原始碼
這裡因為不講 SSR 相關, 所以我在這裡刪除了相關程式碼: loadable.jsx
主體
在此高階元件中, 他的主體是: createLoadableComponent
首先我們看他閉包中所作的東西:
function createLoadableComponent(loadFn, options) {
// loading 的判斷, 忽略
// 建立配置項, 覆蓋預設值
// 其中 render 原始碼: function render(loaded, props) {
// return React.createElement(resolve(loaded), props);
// }
let opts = Object.assign(
{
loader: null,
loading: null,
delay: 200,
timeout: null,
render: render,
webpack: null,
modules: null
},
options
);
// 結果, 用於 呼叫 loader
let res = null;
// 初始化時呼叫, loadFn 函式後面再講
function init() {
if (!res) {
res = loadFn(opts.loader);
}
return res.promise;
}
return class LoadableComponent extends React.Component{
// 這裡先忽略
}
}
返回元件
我們再來看返回的元件:
class LoadableComponent extends React.Component {
constructor(props) {
super(props);
init(); // 在建構函式中啟用初始化函式, 他將 res 賦值為一個 promise
// 定義的 state
this.state = {
error: res.error,
pastDelay: false,
timedOut: false,
loading: res.loading,
loaded: res.loaded
};
}
// 靜態函式, 之前介紹用法的時候說過了
static preload() {
return init();
}
componentWillMount() {
// 用來設定定時器和 delay 相關
this._loadModule();
}
componentDidMount() {
// 標記是否mounted
this._mounted = true;
}
componentWillUnmount() {
// 修改標記, 清除定時器
this._mounted = false;
this._clearTimeouts();
}
render() {
// 渲染函式, 如果當前是 載入中或者錯誤載入的狀態 , 則使用 loading 渲染, 並且傳遞多種引數
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) {
// 如果已經載入完畢, 則呼叫 render 函式, 使用 React.createElement 渲染
return opts.render(this.state.loaded, this.props);
} else {
return null;
}
}
}
load
// 這裡的 load 就是 createLoadableComponent(loadFn, options) 中的入參loadFn
function load(loader) {
// loader 是 options 中的 loader
// 比如: () => import('./my-component')
let promise = loader();
// 用來返回結果
let state = {
loading: true,
loaded: null,
error: null
};
// 一個 promise 賦值, 未呼叫
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;
}
loadMap
呼叫:
Loadable.Map = LoadableMap;
function LoadableMap(opts) {
return createLoadableComponent(loadMap, opts);
}
具體程式碼:
function loadMap(obj) {
let state = {
loading: false,
loaded: {},
error: null
};
let promises = [];
try {
Object.keys(obj).forEach(key => {
let result = load(obj[key]);
if (!result.loading) {
state.loaded[key] = result.loaded;
state.error = result.error;
} else {
state.loading = true;
}
promises.push(result.promise);
result.promise
.then(res => {
state.loaded[key] = res;
})
.catch(err => {
state.error = err;
});
});
} catch (err) {
state.error = err;
}
state.promise = Promise.all(promises)
.then(res => {
state.loading = false;
return res;
})
.catch(err => {
state.loading = false;
throw err;
});
return state;
}
總的來說, 和 load 類似, 利用了 Promise.all
api 來構建了一個 promise 陣列結果
總結
從元件來上看結構:
Loadable()
=== createLoadableComponent(load, opts)
=== class LoadableComponent
從呼叫上來看:
init
呼叫load
函式, 他用來包裝 元件載入後的引數- init 直接返回元件對應 promise 的結果
- 在 render 函式中根據對應結果渲染 loading 元件或者 render 元件
- render 元件利用的是
React.createElement
元件渲染
總的來說去除 SSR 相關, 還是一個比較簡單的元件, 主要的利用還是 ()=>import()
語法的支援