前不久和一个大佬聊了一下之后,愈发觉得体系化的重要性,一个问题丢给你,最终决定你的处理成果的往往是最开始“你对于问题的拆解方式、拆解程度”。
所以对于性能优化这个问题,不是说上来就是无脑使用人家总结的性能优化的具体的方法,而是应该确定你自己的性能指标是啥,然后进行相应的指标信息采集,根据采集分析结果,进行有针对性的优化措施。

性能优化方法论

性能优化体系及关键指标设定

Cgp9HWAuNm2ASt5qAAFXu-AqwBI232.png

  • 性能优化流程
  • 性能指标采集及上报
  • 性能监控预警平台

    只有设定好了性能指标,知道了它的标准,接下来我们才知道该围绕着什么来开展性能优化。

如何设定性能关键指标

  • 可衡量
  • 关注以用户为中心的关键结果和真实体验

    性能瓶颈点:从 URL 输入到页面加载整过程分析

    微信截图_20210329143834.png

    客户端请求阶段

  • 本地缓存

    • 强缓存
    • 协商缓存

      服务端数据处理阶段

  • 数据缓存

    • 借助 Service Worker 的数据接口缓存
    • 借助本地存储的接口缓存(Store 或 localStorage、客户端本身的存储)
    • CDN(Content Delivery Network,内容分发网络)

      案例分析

      微信截图_20210329174015.png
  • 正推:从线索 -> 本质或痛点问题 -> 解决方案 -> 目标

  • 反推:从目标->解决方案->本质或痛点->线索

    指标采集

    指标采集.png

    性能诊断和优化手段

    性能优化.png

    前端性能平台

  • 后台的性能数据处理

  • 前台的可视化展示

    Hybrid 下的优化手段

    性能优化整体分析

    一般 H5 加载大致流程:
    进入 App → 初始化 WebView → 客户端发起请求 → 下载HTML 及 JS/CSS 资源 → 解析JS执行 → JS 请求数据→服务端处理并返回数据 → 客户端解析 DOM 并渲染 → 下载渲染图片 → 完成整体渲染。
    微信截图_20210507211018.png

  • 离线包:将包括 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

微信截图_20210320105444.png

使用 ShouldComponentUpdate 减少 render 不必要的渲染

useMemo、useCallback 实现稳定的 Props 值

发布者订阅者跳过中间组件 Render 过程

将公共数据放在公共祖先上时,在层层向下传递的过程,在某些只是作为数据传递的中间层时会触发不必要的渲染。 使用发布订阅模式,只有在需要该数据时,才去订阅,这样就只有订阅者才会触发 Render 过程

  1. import { useState, useEffect, createContext, useContext } from "react"
  2. const renderCntMap = {}
  3. const renderOnce = name => {
  4. return (renderCntMap[name] = (renderCntMap[name] || 0) + 1)
  5. }
  6. // 将需要公共访问的部分移动到 Context 中进行优化
  7. // Context.Provider 就是发布者
  8. // Context.Consumer 就是消费者
  9. const ValueCtx = createContext()
  10. const CtxContainer = ({ children }) => {
  11. const [cnt, setCnt] = useState(0)
  12. useEffect(() => {
  13. const timer = window.setInterval(() => {
  14. setCnt(v => v + 1)
  15. }, 1000)
  16. return () => clearInterval(timer)
  17. }, [setCnt])
  18. return <ValueCtx.Provider value={cnt}>{children}</ValueCtx.Provider>
  19. }
  20. function CompA({}) {
  21. const cnt = useContext(ValueCtx)
  22. // 组件内使用 cnt
  23. return <div>组件 CompA Render 次数:{renderOnce("CompA")}</div>
  24. }
  25. function CompB({}) {
  26. const cnt = useContext(ValueCtx)
  27. // 组件内使用 cnt
  28. return <div>组件 CompB Render 次数:{renderOnce("CompB")}</div>
  29. }
  30. function CompC({}) {
  31. return <div>组件 CompC Render 次数:{renderOnce("CompC")}</div>
  32. }
  33. export const PubSubCommunicate = () => {
  34. return (
  35. <CtxContainer>
  36. <div>
  37. <h1>优化后场景</h1>
  38. <div>
  39. 将状态提升至最低公共祖先的上层,用 CtxContainer 将其内容包裹。
  40. </div>
  41. <div style={{ marginTop: "20px" }}>
  42. 每次 Render 时,只有组件A和组件B会重新 Render
  43. </div>
  44. <div style={{ marginTop: "40px" }}>
  45. 父组件 Render 次数:{renderOnce("parent")}
  46. </div>
  47. <CompA />
  48. <CompB />
  49. <CompC />
  50. </div>
  51. </CtxContainer>
  52. )
  53. }
  54. export default PubSubCommunicate

immer 的使用

参考资料

  1. React 性能优化 | 包括原理、技巧、Demo、工具使用
  2. 前端性能优化方法与实践
  3. 如何进行 web 性能监控
  4. Implementing Skeleton Screens In React
  5. 我的第一次webpack优化,首屏渲染从9s到1s