react中的内循环与批处理

先有问题再有答案

  1. 要如何理解react内部的事件循环?
  2. UI,状态,副作用的依赖关系是如何描述的?
  3. 如何理解react中的批处理
  4. react内部多次调用setState和异步多次调用有什么区别?

一图胜千文

状态更新

在 React 中,状态更新通常由事件处理器、生命周期方法或副作用(如 useEffect 中的代码)触发。状态更新请求会被 React 调度,这可能会导致组件重新渲染。

视图更新

当状态更新发生时,React 会重新计算组件的渲染输出。这个过程涉及到调用组件的渲染函数或组件树的部分,以生成新的虚拟 DOM。然后,React 对比新旧虚拟 DOM,计算出必要的 DOM 更新,这些更新将同步应用到浏览器的 DOM 上,从而更新用户界面。

副作用执行

副作用(如 useEffect 和 useLayoutEffect)在视图更新后执行。这些副作用可以进行额外的数据获取、订阅、手动更改 DOM 等操作。副作用中也可以进行状态更新,这会再次触发整个更新流程,形成一个可能的循环。

关于批处理

在 React 的同步生命周期方法或事件处理器中,多次连续的状态更新通常会被合并,所以只会引起一次重新渲染。这种行为称为状态更新的批处理(batching)。批处理提高了性能,因为它减少了不必要的重新渲染次数。

在某些情况下,这种批处理机制可能不会按预期工作,导致状态更新被单独处理,从而引起多次渲染。以下是一些批处理可能"失效"或不被应用的情况:
异步操作 :只有同步代码中的状态更新会自动被批处理。在异步操作中(如 setTimeout、Promise、异步事件处理等)触发的状态更新不会被自动批处理,每个状态更新都可能引起一次单独的重新渲染。
非 React 事件处理器:由非 React 的事件管理(如直接添加到 DOM 元素上的事件监听器)触发的状态更新,不会被自动批处理,因为 React 无法捕获和控制这些更新。

一个demo例子

通过如下代码我们可以更深入的理解上图的含义。react版本为17.

jsx 复制代码
import { useEffect, useState } from 'react';
const query = () =>
    new Promise(resolve =>
        setTimeout(() => {
            resolve(1);
        }, 1000),
    );
const useTest = () => {
    const [state1, setState1] = useState(0);
    const [state2, setState2] = useState(0);
    const [state3, setState3] = useState(0);
    const [state4, setState4] = useState(0);

    const queryData = async () => {
        try {
            await query();
            setState1(1);
            setState3(3);
            setState4(4);
        } catch (e) {
            console.log(e);
        }
    };

    useEffect(() => {
        queryData();
    }, []);

    useEffect(() => {
        if (state1) {
            setState2(2);
            console.log('useEffect 改变state2状态');
        }
    }, [state1]);

    useEffect(() => {
        if (state2) {
            console.log('useEffect state2', state2);
        }
    }, [state2]);

    useEffect(() => {
        if (state3) {
            console.log('useEffect state3', state3);
        }
    }, [state3]);

    useEffect(() => {
        if (state4) {
            console.log('useEffect state4', state4);
        }
    }, [state4]);

    console.log('render');

    return {
        state1,
        state2,
        state3,
        state4,
    };
};

思考:上面的运行结果是?

分析运行结果:

  1. 初始化阶段 构建fiber节点并挂载hooks列表 注册副作用函数。

  2. 打印render 渲染初始UI界面.

  3. UI构建完成 依次执行副作用链表。

  4. 第一项执行queryData方法 开始异步请求, 第一个effect执行结束,依次执行完成后续effect列表。

  5. 异步结果返回 执行回调。setState1(1); setState3(3); setState4(4);

  6. 因为当前处于异步函数中 所以这三次state更新会被分到三次不同的队列中 触发三次组件渲染。

  7. 执行setState1(1);后触发UI更新 打印render 执行副作用列表 在副作用中更新了setState2.将这次更新加入任务队列中, 同步打印useEffect 改变state2状态。

  8. 执行任务队列 一次循环清空队列 所以state3 和state2的更新的同一批次的。

  9. 执行渲染 打印render

  10. 完成渲染后触发副作用列表一次打印useEffect state2 2, useEffect state3 3

  11. 继续循环触发setState4 执行render UI渲染完成 打印useEffect state4 4

所以最后的运行结果是:
render
render
useEffect 改变state2状态
render
useEffect state2 2
useEffect state3 3
render
useEffect state4 4

相关推荐
RobinDevNotes1 小时前
从 Electron 到 GPUI,9 个跨平台桌面方案该怎么选
前端·javascript·rust·electron·openresty·前端开发工具
jay神2 小时前
基于 YOLOv8 的轴承缺陷检测(Web 版)
前端·人工智能·yolo
jay神2 小时前
基于 YOLOv8 的森林火灾检测系统(Web 版)
前端·yolo
code2cat2 小时前
HTML基础篇之第一张网页:从index.html到浏览器里的任务清单
前端·html
miss3 小时前
AI 改完不敢上线?OpenSpec 规约驱动开发实战
前端·代码规范·前端工程化
AiMan3 小时前
腾讯ima还是不可以导出左侧所有对话,只能单条导出,方法不对吗
前端
去伪存真3 小时前
并发量一高就装死?Agent 全链路“防猝死”手册
前端·agent
Moment4 小时前
AI 编程时代,聊聊我的 Mac mini 双屏桌面
前端·后端
AiMan4 小时前
先确认能不能一次把Microsofat Copilot所有内容打包一个文件导出
前端
总要学点什么4 小时前
第97篇:三维数据加载-城市建筑白模自定义着色器——让一条光带沿高度扫上去,快慢却由帧率说了算
前端·gis·webgl·cesium·着色器·空间分析·白模