React-loadable source code analysis
In short, loadable is a high-order function, which also uses react's rendering API, webpack knowledge points, babel, and promise combined components
use
First of all, we need to know what is the usage of react-loadable
- loader
For components that need to be lazily loaded, the() => import('xxx')
syntax must be used to use - loading
loading component, props accept error, pastDelay, timedOut, retry parameters, can be customized - delay
Can add delay - timeout
overtime time - render
The type is: (loaded, props)=>ReactNode, additional parameter injection can be added
Load multiple components at the same time
The accepted parameters loader, render, and type are not much different from the above
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}/>;
},
})
Preloading
const LoadableBar = Loadable({
loader: () => import('./Bar'),
loading: Loading,
});
触发:
LoadableBar.preload();
Curry also involves SSR related knowledge points, here is not involved
Source code
Because I don’t talk about SSR related here, I deleted the relevant code here: loadable.jsx
main body
In this high-level component, his main body is: createLoadableComponent
First, let's look at what he did in the closure:
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{
// 这里先忽略
}
}
Return component
Let's look at the returned components again:
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
transfer:
Loadable.Map = LoadableMap;
function LoadableMap(opts) {
return createLoadableComponent(loadMap, opts);
}
Specific code:
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;
}
In general, similar to load, the Promise.all
api is used to construct a promise array result
Summarize
Look at the structure from the component:
Loadable()
=== createLoadableComponent(load, opts)
=== class LoadableComponent
From the point of view of the call:
init
calls theload
function, which is used to package the parameters after the component is loaded- init directly returns the result of the component corresponding to the promise
- Render the loading component or render component according to the corresponding result in the render function
- The render component uses the
React.createElement
component to render
()=>import()
, it is still a relatively simple component to remove SSR related, and the main use is still the support of 0617ebd91d046a syntax
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。