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()
語法的支持