React-按需加载
原理:React.lazy接受一个函数,这个函数需要动态调用imports(),它必须返回一个Promise,该Promise需要resolve一个export default的React组件。利用Suspense接收Promise,执行Promise,最后渲染出组件。React.lazy动态加载依赖于打包工具,比如webpack会把异步引入的文件单独进行打包.
项目中进行按需加载
第一阶段
根据埋点,收集用户点击功能的次数,通过点击次数,确认用户使用频率低的功能,将代码拆分后,在用户操作的时候按需加载,页面渲染时间减少比较有限,但方法确实可行。
第二阶段
将所有不默认在页面中展示的功能,都进行按需加载。尤其是以创建、编辑等操作的Modal弹窗这类的代码,都通过React.lazy处理。页面渲染时间减少20%左右。
第三阶段
扩展到路由页面,整个页面的组件通过React.lazy进行处理
// 对Route中的组件按需加载
const prom: any = () => import('@apps/def-proj/index');
class MainNode extends React.Component<any, any> {
public componentDidMount(): void {
// checkLogin();
listenRouter();
}
public render(): React.ReactNode {
const isOversea = GLOBAL.IS_OVERSEA();
return (
<Switch>
<Route path={ ERouter.HOLMES_BUSINESS_LR_OFFLINE_LIST } render={ () => {
return <LROfflineList />;
} } />
<Route
path={ ERouter.DEF_PROJ_ROOT }
render={
() => <AsyncLoader
compoLazy={ prom }
fallback={ <div /> }
/>
}
/>
<Route path="*">
<Redirect to={ isOversea ? ERouter.HOLMES_BUSINESS_F1SCENECONFIG : ERouter.DEF_PROJ_HOME_PAGE } />
</Route>
</Switch>
);
}
}
封装AsyncLoader
/**
* Copyright (c) 2019-present.
* All rights reserved.
*
* @author link-all
*
* @file SuspenseHolder
*/
import React from 'react';
import { Loading } from '@libs/a-component';
import { Progress } from '@libs/utils';
export interface IPropsAsyncLoader {
compoLazy?(): Promise<{ default: any }>;
compoProps?: any;
fallback?: NonNullable<React.ReactNode> | null;
forbidProgress?: boolean; // 禁用加载进度条
}
const STYLE_Holder: React.CSSProperties = {
height: '80%',
backgroundColor: 'transparent', // 'rgba(255,255,255,0.35)',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
};
/**
* 缓存 importPromise 函数,已加载的外部 js 不再重复加载
*/
const compClasses: Array<{ func: any, comp: any }> = [];
export class AsyncLoader extends React.Component<IPropsAsyncLoader, any> {
private readonly lazyCompo?: React.LazyExoticComponent<any>;
private compClass: any = null;
constructor(props) {
super(props);
if (this.props.compoLazy) {
const item = compClasses.find(cc => cc.func === this.props.compoLazy);
if (item) {
this.compClass = item.comp;
return;
}
!this.props.forbidProgress && Progress.start();
this.lazyCompo = React.lazy(
// @ts-ignore
() => this.props.compoLazy().then(
it => {
!this.props.forbidProgress && Progress.done();
if (!compClasses.find(cc => cc.func === this.props.compoLazy)) {
compClasses.push({
func: this.props.compoLazy,
comp: it.default,
});
// console.log(compClasses.length);
}
return it;
},
),
);
}
}
public UNSAFE_componentWillReceiveProps(nextProps: Readonly<IPropsAsyncLoader>, nextContext: any): void {
if (nextProps.compoLazy !== this.props.compoLazy) {
console.error('compoLazy must be a unique constant varible');
}
}
public render(): React.ReactNode {
if (this.compClass) {
return React.createElement(this.compClass, this.props.compoProps);
}
if (!this.lazyCompo) {
return null;
}
const fallback = this.props.fallback || <div style={ STYLE_Holder }><Loading /></div>;
return (
<React.Suspense fallback={ fallback }>
{
React.createElement(this.lazyCompo, this.props.compoProps)
}
</React.Suspense>
);
}
}
export default AsyncLoader;