首屏加载性能优化(持续记录)

73 阅读2分钟

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;