前不久和一个大佬聊了一下之后,愈发觉得体系化的重要性,一个问题丢给你,最终决定你的处理成果的往往是最开始“你对于问题的拆解方式、拆解程度”。
所以对于性能优化这个问题,不是说上来就是无脑使用人家总结的性能优化的具体的方法,而是应该确定你自己的性能指标是啥,然后进行相应的指标信息采集,根据采集分析结果,进行有针对性的优化措施。
性能优化方法论
性能优化体系及关键指标设定
- 性能优化流程
- 性能指标采集及上报
- 性能监控预警平台
只有设定好了性能指标,知道了它的标准,接下来我们才知道该围绕着什么来开展性能优化。
如何设定性能关键指标
- 可衡量
-
性能瓶颈点:从 URL 输入到页面加载整过程分析
客户端请求阶段
本地缓存
数据缓存
正推:从线索 -> 本质或痛点问题 -> 解决方案 -> 目标
-
指标采集
性能诊断和优化手段
前端性能平台
后台的性能数据处理
-
Hybrid 下的优化手段
性能优化整体分析
一般 H5 加载大致流程:
进入 App → 初始化 WebView → 客户端发起请求 → 下载HTML 及 JS/CSS 资源 → 解析JS执行 → JS 请求数据→服务端处理并返回数据 → 客户端解析 DOM 并渲染 → 下载渲染图片 → 完成整体渲染。 离线包:将包括 HTML、JS、CSS 等页面内静态资源打包到一个压缩包内,App 预先内置该压缩包到本地
React 性能优化
React 工作流
调和(Reconciliation)阶段
- 计算出目标 State 对应的虚拟 DOM 结构 ->>> Render 过程
- 寻找将虚拟 DOM 结构修改为目标虚拟 DOM 结构的最优更新方案 ->> Diff 算法
- 提交阶段(commit)
- 将调和阶段记录的更新方案应用到 DOM 中
- 调用暴露给开发者的钩子方法(componentDidUpdate、useLayoutEffect 等)
实际工程中大部分优化方案都集中在调和阶段的【计算目标虚拟 DOM 结构】过程,其他的大多由 React 内部控制
PureComponent、React.memo
在 React 中,如果父组件状态更新,那么即使传入子组件的 Props 没有修改,也会触发子组件的 Render 过程
利用 PureComponent 和 React.memo 会对 Props 进行浅比较,避免子组件无故渲染
shouldComponentUpdate
使用 ShouldComponentUpdate 减少 render 不必要的渲染
useMemo、useCallback 实现稳定的 Props 值
发布者订阅者跳过中间组件 Render 过程
将公共数据放在公共祖先上时,在层层向下传递的过程,在某些只是作为数据传递的中间层时会触发不必要的渲染。 使用发布订阅模式,只有在需要该数据时,才去订阅,这样就只有订阅者才会触发 Render 过程
import { useState, useEffect, createContext, useContext } from "react"
const renderCntMap = {}
const renderOnce = name => {
return (renderCntMap[name] = (renderCntMap[name] || 0) + 1)
}
// 将需要公共访问的部分移动到 Context 中进行优化
// Context.Provider 就是发布者
// Context.Consumer 就是消费者
const ValueCtx = createContext()
const CtxContainer = ({ children }) => {
const [cnt, setCnt] = useState(0)
useEffect(() => {
const timer = window.setInterval(() => {
setCnt(v => v + 1)
}, 1000)
return () => clearInterval(timer)
}, [setCnt])
return <ValueCtx.Provider value={cnt}>{children}</ValueCtx.Provider>
}
function CompA({}) {
const cnt = useContext(ValueCtx)
// 组件内使用 cnt
return <div>组件 CompA Render 次数:{renderOnce("CompA")}</div>
}
function CompB({}) {
const cnt = useContext(ValueCtx)
// 组件内使用 cnt
return <div>组件 CompB Render 次数:{renderOnce("CompB")}</div>
}
function CompC({}) {
return <div>组件 CompC Render 次数:{renderOnce("CompC")}</div>
}
export const PubSubCommunicate = () => {
return (
<CtxContainer>
<div>
<h1>优化后场景</h1>
<div>
将状态提升至最低公共祖先的上层,用 CtxContainer 将其内容包裹。
</div>
<div style={{ marginTop: "20px" }}>
每次 Render 时,只有组件A和组件B会重新 Render 。
</div>
<div style={{ marginTop: "40px" }}>
父组件 Render 次数:{renderOnce("parent")}
</div>
<CompA />
<CompB />
<CompC />
</div>
</CtxContainer>
)
}
export default PubSubCommunicate