1、react-loadable使用
import { RouteConfig } from 'react-router-config';
import Loadable from 'react-loadable'
import React from 'react';
interface CustomRouteConfig extends RouteConfig {}
const routes: CustomRouteConfig[] = [
{
path: '/',
component: Loadable({
loader: () => import('../layout'),
loading: () => React.createElement('span', {}, 'loading'),
}),
routes: [
{
path: '/keyof',
component: Loadable({
loader: () => import('../pages/Keyof'),
loading: () => React.createElement('span', {}, 'loading')
})
},
{
path: '/match',
component: Loadable({
loader: () => import('../pages/Match'),
loading: () => React.createElement('span', {}, 'loading')
})
},
{
path: '/match/:id',
component: Loadable({
loader: () => import('../pages/Match'),
loading: () => React.createElement('span', {}, 'loading')
})
},
{
path: '/abort',
component: Loadable({
loader: () => import('../pages/AbortController'),
loading: () => React.createElement('span', {}, 'loading')
})
},
]
}
]
export default routes
看看Loadable是如何实现懒加载的:
// Loadable
function Loadable(opts) {
return createLoadableComponent(load, opts);
}
// load函数,用于获取promise对象中的组件
function load(loader) {
var promise = loader();
var state = {
loading: true,
loaded: null,
error: null
};
state.promise = promise.then(function (loaded) {
state.loading = false;
state.loaded = loaded;
return loaded;
}).catch(function (err) {
state.loading = false;
state.error = err;
throw err;
});
return state;
}
// createLoadableComponent
function createLoadableComponent(loadFn, options) {
var _class, _temp;
var res = null;
function init() {
if (!res) {
res = loadFn(opts.loader);
}
return res.promise;
}
return _temp = _class = function (_React$Component) {
_inherits(LoadableComponent, _React$Component);
function LoadableComponent(props) {
_classCallCheck(this, LoadableComponent);
var _this = _possibleConstructorReturn(this, _React$Component.call(this, props));
_this.retry = function () {
_this.setState({ error: null, loading: true, timedOut: false });
res = loadFn(opts.loader);
_this._loadModule();
};
init();
_this.state = {
error: res.error,
pastDelay: false,
timedOut: false,
loading: res.loading,
loaded: res.loaded
};
return _this;
}
LoadableComponent.prototype._clearTimeouts = function _clearTimeouts() {
clearTimeout(this._delay);
clearTimeout(this._timeout);
};
LoadableComponent.prototype.render = function 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;
}
};
return LoadableComponent;
}(React.Component), _class.contextTypes = {
loadable: PropTypes.shape({
report: PropTypes.func.isRequired
})
}, _temp;
}
Loadable是一个高阶组件(hoc),在执行Loadable时会执行createLoadableComponent函数,在createLoadableComponent函数中会执行loader函数得到一个promise对象,这个promise对象包含的是导入的组件,最后会返回一个LoadableComponent组件,该组件的render方法会渲染导入的组件,继而完成组件加载。
2、使用React.Suspense、React.lazy
让我们看看React文档对React.lazy的说法:
React.lazy必须通过调用动态的import()
加载一个函数,此时会返回一个Promise
,并解析(resolve
)为一个带有包含React组件的默认导出的模块。
使用React.Suspense可以优化懒加载前的交互,fallback
属性接受任何在组件加载过程中你想展示的 React 元素。你可以将 Suspense
组件置于懒加载组件之上的任何位置。你甚至可以用一个 Suspense
组件包裹多个懒加载组件。React.lazy加载的组件只能在
import { RouteConfig } from 'react-router-config';
import Loadable from 'react-loadable'
import React from 'react';
import LoadComp from '../components/Loading';
import Layout from '../layout';
interface CustomRouteConfig extends RouteConfig {}
const routes: CustomRouteConfig[] = [
{
path: '/',
// 如果父级和子级路由都是用lazy、suspense会导致子级路由不会渲染
// component: LoadComp(React.lazy(() => import('../layout'))),
component: Layout,
routes: [
{
path: '/keyof',
component: LoadComp(React.lazy(() => import('../pages/Keyof'))),
},
{
path: '/match/:id',
component: LoadComp(React.lazy(() => import('../pages/Match'))),
},
{
path: '/abort',
component: LoadComp(React.lazy(() => import('../pages/AbortController'))),
},
]
}
]
export default routes
// loadComp高阶组件
import React from 'react';
import Loader from 'react-loader-spinner';
const loadComp = (Com: React.LazyExoticComponent) => {
return class LoadComp extends React.Component {
render() {
return (
}>
)
}
}
}
export default loadComp;
3、替换前后对比
对比react-loadable,React.Suspense还是有一些不足。
- 加载组件缺少delay参数,不能解决请求快速完成时的"闪烁"问题(即使加载模块只需要几毫秒的时间, fallback也会被执行,就上述代码来说,也就是 Spinner 会闪烁一下),需要自己封装
- 没有内置的加载错误处理方法,需要自己去处理
- react-loadable有预加载支持,React.Lazy没有
仍然推荐使用react-loadable来异步加载组件,暂时不用React.Suspense。