检查运行时性能
检查pecker.oa.com, Performance 和 Memory面板
在页面上点击交互, 切换路由, 查询, 内存占用平稳. 检查Memory标签未发现明显内存泄漏问题.
图中上方帧数降低主要出现在在页面触发submit事件后大规模重新计算样式与重排时.
运行时性能还不错, 主要优化方向集中在打包, 加载与网络优化.
文件加载优化
来看这个图, 可以看到chunk.js 在 gziped 后单文件太大, 占了 1.2Mb. 三五百kb的大小应该比较合理.
最左侧的antd必须引入. 而图中间一列, recharts和highcharts也占了不小的空间, moment的国际化也占了不小.
大js带来的问题是下载单文件与解析会变慢, 见图:
通过Lighthouse的测试, FCP 略慢 (超过2s). 上图说明此文件在本机上的执行所用CPU时间占了1.2秒. 应缩减此js文件大小.
目前应该先不用太在意Skipping Effects: 指 React.memo, useEffect, PureComponent, shouldComponentUpdate, redux 这种运行时性能优化.
当前打包出来的大 js-bundle, 没有做代码拆分, 初次打开得等下载解析完才会加载出页面, 比较影响性能。
考虑使用 React.lazy + Suspense
React.lazy
方法是内置的code-splitting工具, 可以像渲染一个普通组件那样去渲染一个动态引入组件. 可以方便地将独立的组件分离为单独的js-chunk.
// 修改前
import OtherComponent from './OtherComponent';
// 修改后
const OtherComponent = React.lazy(() => import('./OtherComponent'));
第一次渲染这个组件时, 这个方法将自动加载包含OtherComponent
的js-bundle, React.lazy
结合动态import
相应的js会被webpack拆分, 在使用时才加载. React.lazy
接受个函数, 函数中必须调用一个动态import()
, 动态import()
会返回一个Promise, 这个Promis会"resolve"出一个export default React组件
的 module.
这种用 lazy() 加载的组件, 可以渲染在Suspense
组件中, 通过fallback
属性, 可以方便的去管理它loading时, 以及加载失败时要展示的组件:
import React, { Suspense } from 'react';
const OtherComponent = React.lazy(() => import('./OtherComponent'));
function MyComponent() {
return (
<div>
<Suspense fallback={<div>Loading...</div>}>
<OtherComponent />
</Suspense>
</div>
);
}
因此大概从下面三个方向进行动态 import
1. 路由懒加载
老生常谈的路由懒加载. 在项目中的路由部分使用lazy
也比较合理, 因为浏览传统网站的逻辑就是点击链接时要等待一定时间来加载. 下方是一个例子.
import React, { Suspense, lazy } from 'react';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
const Home = lazy(() => import('./routes/Home'));
const About = lazy(() => import('./routes/About'));
const App = () => (
<Router>
<Suspense fallback={<div>Loading...</div>}>
<Switch>
<Route exact path="/" component={Home}/>
<Route path="/about" component={About}/>
</Switch>
</Suspense>
</Router>
);
另一个例子
function lazyload(loader: () => Promise<{ default: React.ComponentType<any> }>) {
const LazyComponent = React.lazy(loader)
const Lazyload: React.FC = (props: any) => {
return (
<React.Suspense fallback={<Spinner/>}>
<LazyComponent {...props}/>
</React.Suspense>
)
}
return Lazyload
}
const Login = lazyload(() => import('src/pages/Home'))
不过现在有一个问题, 我们用的路由是 rsf 中的 ConfigRouters, 其中的Route组件中的属性值是用变量使用的, 但动态import
不允许使用变量.
2. React 组件懒加载
使用这几个工具可以更轻松地从组件层面对进行代码进行拆分.
下面是一个使用了 React.lazy
但没用 Suspense
的例子
import React, { lazy } from 'react';
const AvatarComponent = lazy(() => import('./AvatarComponent'));
const DetailsComponent = () => (
<div>
<AvatarComponent />
</div>
)
这里有个将React.lazy
与Suspense
结合起来的演示: react-lazy-suspense, 打开开发者工具可以看到, 当点了 Click Me 按钮后, 会加载要展示组件单独的js文件.
目前的想法是将 CommonPage 组件中, 除了QueryForm, 下方查询的 Container 部分用这种方法处理.
3. 第三方库与本地文件懒加载
对于某些引入的不太常用的第三方js, 可用import()
在用到时动态加载
async function encrypt(value: string): Promise<string> {
// 改为 import() 的方式引入第三方库
const module = await import('jsencript')
// expor default 导出的模块
const JSEncrypt = module.default
// 配合引入的 JSEncrypt 加密
const encrypt = new JSEncrypt()
encrypt.setPublicKey(PUBLIC_KEY)
const encrypted = encrypt.encrypt(value)
return encrypted
}
这可以用来懒加载项目中的 JSON 文件等,通过 import() 方式懒加载的代码或者 JSON 文件,同样会经过 webpack 处理, 例如项目中用到了存储展示状态的大号JSON文件, 就可以像这样动态导入:
const module = await import('./errorStatus.json')
console.log(module.default)
项目中用到的high-chart
与recharts
, 应该是比较适用于这种处理方式, 但这种已经被封装在shared-component
中的该如何懒加载? 这俩第三方库占的空间不小了, 懒加载对于提升首屏加载速度应该效果明显.
参考链接:
react code splitting
reactlazy官方文档
http/2
服务器目前在用http/1.1, 启用 http/2 应该会加快多个请求资源的速度. 不过现在查询接口较慢, 查询条件又比较灵活, 接口侧不太容易做优化. 配置方式:
Nginx
server {
listen 443 http2;
server_name xxx.xxx;
}
moment优化
我们可能不需要加载整个moments, 比如moment/locale/*.js
中的国际化文件, 除了英语和zh-cn可以都不要
可以借助webpack.ignorePlugin
排除.
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
Webpack
官网此插件的介绍就是用moment举例的: Webpack-ignore-plugin
lodash优化
其实lodash占得空间并不大, 优化后减小文件效果应该不会很明显, 稍微带过.
保证引入时为
import debounce from 'lodash/debounce'
而不是
import lodash from 'lodash'
// 或者
import { debounce } from 'lodash'
即可.