react-loadable组件加上import(),可以轻松实现以组件为中心代码的分割。
主体流程
源码截图:
export出来的是Loadable,而Loadable里面调用的是createLoadableComponent函数。
createLoadableComponent函数有两个参数,loadFn即是load函数,options是传入的参数。
在createLoadableComponent中,先判断了options.loading是否存在,不存在则扔出一个错误。
if (!options.loading) {
throw new Error("react-loadable requires a `loading` component");
}
后定义了一个opts、res变量,声明init函数等,最后返回一个LoadableComponent。LoadableComponent先运行了init函数。
function init() {
if (!res) {
res = loadFn(opts.loader);
}
return res.promise;
}
init函数中,调用load函数,并传入入参opts.loader(loader即类似() => import("./pages/Data/DataSource"),)。import()函数运行时加载模块。import()返回一个 Promise 对象。import()加载模块成功以后,这个模块会作为一个对象,当作then方法的参数。
function load(loader) {
// 运行import()
let promise = loader();
let state = {
loading: true, // 是否加载中
loaded: null, // loaded即已加载成功的模块
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;
}
然后声明state
this.state = {
error: res.error,
pastDelay: false,
timedOut: false,
loading: res.loading,
loaded: res.loaded
};
componentWillMount函数里面运行了_loadModule函数。
主要操作:当组件加载成功或失败时,更新state的error、loaded和loading
_loadModule() {
if (this.context.loadable && Array.isArray(opts.modules)) {
opts.modules.forEach(moduleName => {
this.context.loadable.report(moduleName);
});
}
// 判断是否还在加载,已加载完成,则返回
if (!res.loading) {
return;
}
// 用于更新state
let setStateWithMountCheck = (newState) => {
if (!this._mounted) {
return;
}
this.setState(newState);
}
// delay延时时间,加定时器,当超过delay设置的值,将pastDelay设置为true
if (typeof opts.delay === 'number') {
if (opts.delay === 0) {
this.setState({ pastDelay: true });
} else {
this._delay = setTimeout(() => {
setStateWithMountCheck({ pastDelay: true });
}, opts.delay);
}
}
// timeout超时时间,加定时器,当超过timeout设置的值,将timeout设置为true
if (typeof opts.timeout === "number") {
this._timeout = setTimeout(() => {
setStateWithMountCheck({ timedOut: true });
}, opts.timeout);
}
// 更新error、loaded和loading,并清除定时器
let update = () => {
setStateWithMountCheck({
error: res.error,
loaded: res.loaded,
loading: res.loading
});
this._clearTimeouts();
};
// 当组件加载成功或失败,则更新state
res.promise
.then(() => {
update();
return null;
})
.catch(err => {
update();
return null;
});
}
componentDidMount函数里设置this._mounted为true
componentWillUnmount() {
this._mounted = false;
this._clearTimeouts();
}
组件卸载前设置_mounted为false,并取消定时器。
最后是render。
render() {
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) {
return opts.render(this.state.loaded, this.props);
} else {
return null;
}
}
若组件还在加载,或加载出错,则显示传入的loading过渡组件。若组件已加载成功,则调用render函数进行渲染。
// 若__esModule为true,则表明是es模块,那么返回obj.default,否则返回obj。
function resolve(obj) {
return obj && obj.__esModule ? obj.default : obj;
}
// props为原组件的props
function render(loaded, props) {
return React.createElement(resolve(loaded), props);
}
Loadable.Map
用于并行加载多个模块。Loadable.Map实质是调用了LoadableMap函数。
function LoadableMap(opts) {
if (typeof opts.render !== "function") {
throw new Error("LoadableMap requires a `render(loaded, props)` function");
}
return createLoadableComponent(loadMap, opts);
}
LoadableMap函数与主流程的运行并无不同,除了所用的loadFn和render不一样。Loadable.Map使用的loadMap函数,并且render函数需要自己传入。
function loadMap(obj) {
let state = {
loading: false,
loaded: {},// 存放所有加载成功的模块
error: null
};
let promises = [];
try {
// 依次遍历传入的loader
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.loading的状态。
// 这个代码里面的res和err看着应该是没值的
state.promise = Promise.all(promises)
.then(res => {
state.loading = false;
return res;
})
.catch(err => {
state.loading = false;
throw err;
});
return state;
}
Loadable.Capture
Loadable.Capture用于收集已呈现的所有模块。源码很简单,就不显示了。
主要是定义了一个loadable,loadable是一个context。loadable的report被props.report赋值。这个report哪里会用到呢?在前面的主体流程的_loadModule里面有这样一段代码:
if (this.context.loadable && Array.isArray(opts.modules)) {
opts.modules.forEach(moduleName => {
this.context.loadable.report(moduleName);
});
}
当loadable和opts.modules均存在时,将opts.modules里面的数据取出来传入report函数。
用法
let modules = [];
......
<Loadable.Capture report={moduleName => modules.push(moduleName)}>
<App/>
</Loadable.Capture>
Loadable.preloadAll和Loadable.preloadReady
Loadable.preloadAll用于预加载模块,ALL_INITIALIZERS里面放的是所有模块的init函数。
Loadable.preloadAll = () => {
return new Promise((resolve, reject) => {
flushInitializers(ALL_INITIALIZERS).then(resolve, reject);
});
};
Loadable.preloadReady用于检查在浏览器上模块是否已经加载完成了。READY_INITIALIZERS里面放的是init函数运行返回的promise
Loadable.preloadReady = () => {
return new Promise((resolve, reject) => {
// We always will resolve, errors should be handled within loading UIs.
flushInitializers(READY_INITIALIZERS).then(resolve, resolve);
});
};
参考自: