沐游虞笔记
  • 前端面试题

    • HTML面试题汇总(无答案)
    • HTML面试题汇总
    • CSS 面试题汇总(无答案)
    • CSS 面试题汇总
    • javascript 面试题汇总(无答案)
    • javascript 面试题汇总
    • promise 面试题(无答案)
    • promise 面试题
    • 浏览器面试题汇总(无答案)
    • 浏览器面试题汇总
    • 网络面试题汇总(无答案)
    • 网络面试题汇总
    • 工程化面试题汇总(无答案)
    • 工程化面试题汇总
    • VUE面试题汇总(无答案)
    • VUE面试题汇总
  • 直播课文件

    • 静态页面学习指导
    • 属性的计算过程
    • 层叠继承规则总结
    • BFC
    • JS基础知识回顾
    • DOM 事件的传播机制
    • DOM 事件的注册和移除
    • 阻止事件默认行为
    • 基础领航考试题
    • 基础领航考试题(答案)
    • 2024前端发展
    • JS核心概念学习指导
    • 第三方库与工程化学习指导
    • Vue入门学习指导
    • vue进阶学习指导
    • 前端性能优化
  • 笔面试环节知识讲解

    • 目录
    • 图像处理
    • 图像处理(面试)
    • Webpack构建优化
    • Webpack构建优化(面试)
    • TTS性能优化
    • TTS性能优化(面试)
    • 实时协作
    • 实时协作(面试)
    • 网页复制图片到剪贴板
    • 网页复制图片到剪贴板(面试)
    • vite插件
    • vite插件(面试)
    • 表单数据同步与保持
    • 表单数据同步与保持(面试)
    • 优化虚拟列表
    • 优化虚拟列表(面试)
    • 微前端解决巨石应用
    • 微前端解决巨石应用(面试)
    • DNS解析与优化
    • DNS解析与优化(面试)
    • 前端监控
    • 前端监控(面试)
    • 12.跨标签页通信
    • 12.跨标签页通信(面试)
    • 13.Vite相关优化
    • 13.Vite相关优化(面试)
    • 14.计时器节流问题
    • 14.计时器节流问题(面试)
    • 15.多文件预览支持
    • 15.多文件预览支持(面试)
    • 16.defer优化白屏时间
    • 16.defer优化白屏时间(面试)
  • Vue3整体变化
  • Vue2响应式回顾
  • Vue3响应式变化
  • nextTick实现原理
  • 两道代码题
  • Vue运行机制
  • 渲染器核心功能
  • 事件绑定与更新
  • computed面试题
  • watch面试题
  • 图解双端diff
  • 图解快速dff
  • 最长递增子序列
  • 模板编译器
  • 模板编译提升
  • 组件name作用
  • 路由传参方式
  • 基础篇

    • 序章React介绍
    • JSX基础语法
    • React基本介绍
    • 表单
    • 生命周期
    • 组件与事件绑定
    • 组件状态与数据传递
    • Hooks
    • React--redux介绍
    • React-router介绍
  • 就业篇

    • 属性默认值和类型验证
    • 高阶组件
    • Ref
    • Context
    • Render Props
    • Portals
    • 错误边界
    • 组件渲染性能优化
    • 前端框架的理解
    • Reacti和Vue描述页面的区别
    • 前端框架的分类
    • 虚拟DoM
    • React整体架构
    • React渲染流程
    • Fiber双缓冲
    • MessageChannel
    • Scheduleri调度普通任务
    • Scheduleri调度延时任务
    • 最小堆
    • React中的位运算
    • beginWork工作流程
    • completeWork工作流程
    • 图解diff算法
    • commit工作流程
    • lane模型
    • React中的事件
    • Hooks原理
    • useStateuseReducer.
    • effect相关hook
    • useCallbackuseMemo
    • useRef
    • Update
    • 性能优化策略之eagerState
    • 性能优化策略之bailout
    • bailoutContextAPl
    • 性能优化对日常开发启示
  • 前端监控概述
  • 错误监控
  • 数据上报
  • 页面性能监控
  • 用户行为收集与埋点
  • CSS3手册
  • HTML5手册
  • JavaScript语言提升

    • es补充
    • 事件循环
    • promise基础
    • Promise的链式调用
    • Promise的静态方法
    • async和await
    • Promise相关面试题
  • 网络

    • 客户端与服务器
    • 关于 Apifox 的使用
  • git文档
  • 工程化

    • CommonJS
    • ES module
    • npm文档(包管理)
    • Lass笔记
    • webpack工具
  • canvas详解
  • uinapp笔记
  • 自动化测试
  • oauth2令牌

    • 认识Oauth2
    • 三方应用实现github授权
    • 微信三方应用登录实现
    • 支付宝沙箱支付功能
  • 前端面试题

    • HTML面试题汇总(无答案)
    • HTML面试题汇总
    • CSS 面试题汇总(无答案)
    • CSS 面试题汇总
    • javascript 面试题汇总(无答案)
    • javascript 面试题汇总
    • promise 面试题(无答案)
    • promise 面试题
    • 浏览器面试题汇总(无答案)
    • 浏览器面试题汇总
    • 网络面试题汇总(无答案)
    • 网络面试题汇总
    • 工程化面试题汇总(无答案)
    • 工程化面试题汇总
    • VUE面试题汇总(无答案)
    • VUE面试题汇总
  • 直播课文件

    • 静态页面学习指导
    • 属性的计算过程
    • 层叠继承规则总结
    • BFC
    • JS基础知识回顾
    • DOM 事件的传播机制
    • DOM 事件的注册和移除
    • 阻止事件默认行为
    • 基础领航考试题
    • 基础领航考试题(答案)
    • 2024前端发展
    • JS核心概念学习指导
    • 第三方库与工程化学习指导
    • Vue入门学习指导
    • vue进阶学习指导
    • 前端性能优化
  • 笔面试环节知识讲解

    • 目录
    • 图像处理
    • 图像处理(面试)
    • Webpack构建优化
    • Webpack构建优化(面试)
    • TTS性能优化
    • TTS性能优化(面试)
    • 实时协作
    • 实时协作(面试)
    • 网页复制图片到剪贴板
    • 网页复制图片到剪贴板(面试)
    • vite插件
    • vite插件(面试)
    • 表单数据同步与保持
    • 表单数据同步与保持(面试)
    • 优化虚拟列表
    • 优化虚拟列表(面试)
    • 微前端解决巨石应用
    • 微前端解决巨石应用(面试)
    • DNS解析与优化
    • DNS解析与优化(面试)
    • 前端监控
    • 前端监控(面试)
    • 12.跨标签页通信
    • 12.跨标签页通信(面试)
    • 13.Vite相关优化
    • 13.Vite相关优化(面试)
    • 14.计时器节流问题
    • 14.计时器节流问题(面试)
    • 15.多文件预览支持
    • 15.多文件预览支持(面试)
    • 16.defer优化白屏时间
    • 16.defer优化白屏时间(面试)
  • Vue3整体变化
  • Vue2响应式回顾
  • Vue3响应式变化
  • nextTick实现原理
  • 两道代码题
  • Vue运行机制
  • 渲染器核心功能
  • 事件绑定与更新
  • computed面试题
  • watch面试题
  • 图解双端diff
  • 图解快速dff
  • 最长递增子序列
  • 模板编译器
  • 模板编译提升
  • 组件name作用
  • 路由传参方式
  • 基础篇

    • 序章React介绍
    • JSX基础语法
    • React基本介绍
    • 表单
    • 生命周期
    • 组件与事件绑定
    • 组件状态与数据传递
    • Hooks
    • React--redux介绍
    • React-router介绍
  • 就业篇

    • 属性默认值和类型验证
    • 高阶组件
    • Ref
    • Context
    • Render Props
    • Portals
    • 错误边界
    • 组件渲染性能优化
    • 前端框架的理解
    • Reacti和Vue描述页面的区别
    • 前端框架的分类
    • 虚拟DoM
    • React整体架构
    • React渲染流程
    • Fiber双缓冲
    • MessageChannel
    • Scheduleri调度普通任务
    • Scheduleri调度延时任务
    • 最小堆
    • React中的位运算
    • beginWork工作流程
    • completeWork工作流程
    • 图解diff算法
    • commit工作流程
    • lane模型
    • React中的事件
    • Hooks原理
    • useStateuseReducer.
    • effect相关hook
    • useCallbackuseMemo
    • useRef
    • Update
    • 性能优化策略之eagerState
    • 性能优化策略之bailout
    • bailoutContextAPl
    • 性能优化对日常开发启示
  • 前端监控概述
  • 错误监控
  • 数据上报
  • 页面性能监控
  • 用户行为收集与埋点
  • CSS3手册
  • HTML5手册
  • JavaScript语言提升

    • es补充
    • 事件循环
    • promise基础
    • Promise的链式调用
    • Promise的静态方法
    • async和await
    • Promise相关面试题
  • 网络

    • 客户端与服务器
    • 关于 Apifox 的使用
  • git文档
  • 工程化

    • CommonJS
    • ES module
    • npm文档(包管理)
    • Lass笔记
    • webpack工具
  • canvas详解
  • uinapp笔记
  • 自动化测试
  • oauth2令牌

    • 认识Oauth2
    • 三方应用实现github授权
    • 微信三方应用登录实现
    • 支付宝沙箱支付功能
  • 基础篇

    • 序章:React课程介绍
    • JSX 基础语法
    • React 基本介绍
    • 表单
    • 生命周期
    • 组件与事件绑定
    • 组件状态与数据传递
    • Hooks
    • React-redux 介绍
    • React-router介绍
  • 就业篇

    • 属性默认值和类型验证
    • 高阶组件
    • Ref
    • Context
    • Render Props
    • Portals
    • 错误边界
    • 组件渲染性能优化
    • 前端框架的理解
    • React和Vue描述页面的区别
    • 前端框架的分类
    • 虚拟 DOM
    • React 整体架构
    • React 渲染流程
    • Fiber双缓冲
    • MessageChannel
    • Scheduler调度普通任务
    • Scheduler调度延时任务
    • 最小堆
    • React中的位运算
    • beginWork工作流程
    • completeWork工作流程
    • 图解diff算法
    • commit 工作流程
    • lane模型
    • React 中的事件
    • Hooks原理
    • useState和useReducer
    • effect相关hook
    • useCallback和useMemo
    • useRef
    • Update
    • 性能优化策略之eagerState
      • eagerState 策略
      • 真题解答
    • 性能优化策略之bailout
    • bailout和ContextAPI
    • 性能优化对日常开发启示
  • react
  • 就业篇
luzhichang
2024-10-17
目录

性能优化策略之eagerState

# 性能优化策略之eagerState

面试题:谈一谈 React 中的 eagerState 策略是什么?

在 React 中,有很多和性能优化相关的 API:

  • shouldComponentUpdate
  • PureComponent
  • React.memo
  • useMemo
  • useCallback

实际上,开发者调用上面的 API,内部是在命中 React 的性能优化策略:

  • eagerState
  • bailout
import { useState } from "react";

// 子组件
function Child() {
  console.log("child render");
  return <span>child</span>;
}

// 父组件
function App() {
  const [num, updateNum] = useState(0);
  console.log("App render", num);

  return (
    <div onClick={() => updateNum(1)}>
      <Child />
    </div>
  );
}

在上面的代码中,渲染结果如下:

首次渲染:

App render 0
child render

第一次点击

App render 1
child render

第二次点击

App render 1

第三次以及之后的点击

不会有打印

上面的这个例子实际上就涉及到了我们所提到的 React 内部的两种性能优化策略:

  • 在第二次打印的时候,并没有打印 child render,此时实际上是命中了 bailout 策略。命中该策略的组件的子组件会跳过 reconcile 过程,也就是说子组件不会进入 render 阶段。
  • 后面的第三次以及之后的点击,没有任何输入,说明 App、Child 都没有进入 render 阶段,此时命中的就是 eagerState 策略,这是一种发生于触发状态更新时的优化策略,如果命中了该策略,此次更新不会进入 schedule 阶段,更不会进入 render 阶段。

# eagerState 策略

该策略的逻辑其实是很简单:如果某个状态更新前后没有变化,那么就可以跳过后续的更新流程。

state 是基于 update 计算出来的,计算过程发生在 render 的 beginWork,而 eagerState 则是将计算过程提前到了 shcedule 之前执行。

该策略有一个前提条件,那就是当前的 FiberNode 不存在待执行的更新,因为如果不存在待执行的更新,那么当前的更新就是第一个更新,那么计算出来的 state 即便有变化也可以作为后续更新的基础 state 来使用。

例如,在使用 useState 触发更新的时候,对应的 dispatchSetState 逻辑如下:

if (
  fiber.lanes === NoLanes &&
  (alternate === null || alternate.lanes === NoLanes)
) {
  // 队列当前为空,这意味着我们可以在进入渲染阶段之前急切地计算下一个状态。 如果新状态与当前状态相同,我们或许可以完全摆脱困境。
  const lastRenderedReducer = queue.lastRenderedReducer;
  if (lastRenderedReducer !== null) {
    let prevDispatcher;
    try {
      const currentState = queue.lastRenderedState; // 也就是 memoizedState
      const eagerState = lastRenderedReducer(currentState, action); // 基于 action 提前计算 state
      // 将急切计算的状态和用于计算它的缩减器存储在更新对象上。 
      // 如果在我们进入渲染阶段时 reducer 没有改变,那么可以使用 eager 状态而无需再次调用 reducer。
      update.hasEagerState = true; // 标记该 update 存在 eagerState
      update.eagerState = eagerState; // 存储 eagerState 的值
      if (is(eagerState, currentState)) {
        // ...
        return;
      }
    } catch (error) {
      // ...
    } finally {
      // ...
    }
  }
}

在上面的代码中,首先通过 lastRenderedReducer 来提前计算 state,计算完成后在当前的 update 上面进行标记,之后使用 is(eagerState, currentState) 判断更新后的状态是否有变化,如果进入 if,说明更新前后的状态没有变化,此时就会命中 eagerState 策略,不会进入 schedule 阶段。

即便不为 true,由于当前的更新是该 FiberNode 的第一个更新,因此可以作为后续更新的基础 state,因此这就是为什么在 FC 组件类型的 update 里面有 hasEagerState 以及 eagerState 字段的原因:

const update = {
  hasEagerState: false,
  eagerState: null,
  // ...
}

在上面的示例中,比较奇怪的是第二次点击,在第二次点击之前,num 已经为 1 了,但是父组件仍然重新渲染了一次,为什么这种情况没有命中 eagerState 策略?

FiberNode 分为 current 和 wip 两种。

在上面的判断中,实际上会对 current 和 wip 都进行判断,判断的条件为两个 Fiber.lanes 必须要为 NoLanes

if (
  fiber.lanes === NoLanes &&
  (alternate === null || alternate.lanes === NoLanes)
){
  // ....
}

对于第一次更新,当 beginWork 开始前,current.lanes 和 wip.lanes 都不是 NoLanes。当 beginWork 执行后, wip.lanes 会被重置为 NoLanes,但是 current.lanes 并不会,current 和 wip 会在 commit 阶段之后才进行互换,这就是为什么第二次没有命中 eagerState 的原因。

那么为什么后面的点击又命中了呢?

虽然上一次点击没有命中 eagerState 策略,但是命中了 bailout 策略,对于命中了 bailout 策略的 FC,会执行 bailoutHooks 方法:

function bailoutHooks(
  current: Fiber,
  workInProgress: Fiber,
  lanes: Lanes,
) {
  workInProgress.updateQueue = current.updateQueue;
  // ...
  current.lanes = removeLanes(current.lanes, lanes);
}

在执行 bailoutHooks 方法的时候,最后一句会将当前 FiberNode 的 lanes 移除,因此当这一轮更新完成后,current.lanes 和 wip.lanes 就均为 NoLanes,所以在后续的点击中就会命中 eagerState 策略。

# 真题解答

题目:谈一谈 React 中的 eagerState 策略是什么?

参考答案:

在 React 内部,性能优化策略可以分为:

  • eagerState 策略
  • bailout 策略

eagerState 的核心逻辑是如果某个状态更新前后没有变化,则可以跳过后续的更新流程。该策略将状态的计算提前到了 schedule 阶段之前。当有 FiberNode 命中 eagerState 策略后,就不会再进入 schedule 阶段,直接使用上一次的状态。

该策略有一个前提条件,那就是当前的 FiberNode 不存在待执行的更新,因为如果不存在待执行的更新,当前的更新就是第一个更新,计算出来的 state 即便不能命中 eagerState,也能够在后面作为基础 state 来使用,这就是为什么 FC 所使用的 Update 数据中有 hasEagerState 以及 eagerState 字段的原因。

Update
性能优化策略之bailout

← Update 性能优化策略之bailout→

Theme by Vdoing | Copyright © 2021-2024 蜀ICP备2024068710号-1
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式