持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第21天,点击查看活动详情
业务背景
前端优化是一个永恒的课题,在优化的过程中,我们需要结合具体的项目,确定优化的目标和预期,制定优化方案,同时做好优化技术后复盘。下面将介绍一些我在过去几年中写react代码中常用的优化细节。
为什么要做项目优化
1.减少用户流失
用户角度:页面加载快,操作响应及时,体验好
2.更好的创收
服务角度:减少页面请求的次数,减少请求使用的带宽,节省可观的资源
时间角度的优化
1.网络请求优化 减少DNS耗时,CDN,ServiceWorker和HTTP缓存,PWA等技术,tree-shaking,移除无用代码,代码分割,使用HTTP2,资源压缩
- 首屏加载优化
减少白屏时间,分屏分片加载,按需加载,预加载和预请求,资源离线缓存
- 渲染过程优化
GPU加速,减少DOM重排和重绘,资源预加载
基于react颗粒性的优化
减少非必要的渲染和计算
- ShouldComponentUpdate
- PureComponent和React.memo
- unstable_batchedUpdates批量更新
- useMemo和useCallback稳定props的值
其他业务优化
- 图片组件懒加载(非可见元素滞后渲染,快速render)
- 商品图片组件使用缩略图(快速渲染,减少网络开销)
- 埋点系统的解耦:class Component(配置埋点文件,使用call和apply达到埋点的解耦),hooks重写 createElement和props.onClick监听达到无痕埋点
React输入大量表单渲染时的性能优化
方案建议:
-
运用debounce避免在输入时频繁大量的调用方法,重复的调用会对中大型应用产生影响性能,造成组件及其子树不必要地更新渲染。
-
运用shouldComponentUpdate来拦截父组件更新是造成的子组件的不必要更新,以减少render函数的执行。
业务背景:
大量的报告表单,在小程序和后台管理中渲染时,大量的canvas和form表单一次性渲染造成页面卡顿,在表单输入处理时的更新操作也会卡顿
问题原因:
技术排查后,发现这个输入框没有使用debounce降低接口调用的频率,整个页面的部分子组件也没有使用shouldComponentUpdate来避免不必要的render。即定制师每输入或修改一个字符,都会选择整个页面,并请求接口,继续渲染整个页面,从而产生了大量不必要的界面重复渲染,造成卡顿。
解决方案:
1. 使用debounce优化
InputA = reactComponentDebounce({
triggerMs: DELAY,
})(Input)
复制代码
额外注意debounce发请求问题:
当第counter个接口返回数据时,若与当前调用次数不匹配时,应拒绝将错误数据渲染到页面
DebouncePatten(params, fnArray) {
const current = ++COUNTER
return fnArray.reduce((prev, cur) => prev.then(wrapWithCancel(cur)), Promise.resolve(params));
functionwrapWithCancel(fn) {
return (data) => {
if (current === COUNTER) {
returnfn(data);
} else {
returnPromise.reject('cancelled');
}
}
}
}
复制代码
2.使用shouldComponentUpdate或者pureComponent优化
1). 父组件更新时,用shouldComponentUpdate拦截子组件的不必要的render。返回false时,componentWillUpdate(),render()和componentDidUpdate()都不会被调用,即该组件不会更新。当父组件返回true时,整个过程会像叶子节点传递,即叶子节点A与B都会被要求执行更新的生命周期,如果A的shouldComponentUpdate对比其范围内的props和state没有变化而返回false,B返回true,那么只有B更新,从而实现有效的渲染拦截。同时,shouldComponentUpdate内部的判断需高效且正确,如果内部判断开销大于virtual DOM的对比更新的开销,就并没有有效的实现优化;对比中涉及到深浅拷贝的问题也需注意,以保证正确性。
shouldComponentUpdate(newProps, newState) {
const oldProps = this.props
if (oldProps.data === newProps.data)
{
return false
}
return true
}
复制代码
实际修改
-
将庞大的父组件抽离成多个子组件,使用shouldComponentUpdate或优化更新效率并减少不必要重绘。
-
优化已有的shouldComponentUpdate,去除不必要的判断以及未维护的错误判断。
-
注意表单提交时的校验状态容易被shouldComponentUpdate返回false处理而忽视。