React18中setState

1 setState基本用法

React中的setState是用于更新组件状态的API,是类组件中管理状态的主要方式。在React 18中,setState有三种主要使用形式:

1.1 对象式setState

javascript 复制代码
this.setState({ 
  count: this.state.count + 1 
});

1.2 函数式setState

javascript 复制代码
this.setState((prevState, props) => {
  return {
    count: prevState.count + 1
  };
});

1.3 带回调的setState

javascript 复制代码
this.setState({
  count: this.state.count + 1
}, () => {
  console.log('状态更新完毕', this.state.count);
});

函数式setState允许你基于先前的状态和属性计算新状态,这特别适用于多次状态更新依赖先前状态的情况。带回调的setState则在状态更新和界面重新渲染后执行,可用于获取更新后的状态值。

2 React18中setState的特性

2.1 异步批处理

在React 18中,setState默认是异步操作,且无论在什么环境下执行(合成事件、生命周期、定时器、原生事件等),都会自动进行批处理。

javascript 复制代码
handleClick = () => {
  // 三次setState会被批处理为一次更新
  this.setState({ count: this.state.count + 1 });
  this.setState({ count: this.state.count + 1 }); 
  this.setState({ count: this.state.count + 1 });
  
  // 此时state.count还是原来的值
  console.log(this.state.count); // 0
  
  // 通过回调函数获取更新后的值
  this.setState({ count: this.state.count + 1 }, () => {
    console.log(this.state.count); // 1(批处理后的结果)
  });
}

2.2 与React16行为的对比

场景 React 16 React 18
合成事件/生命周期 异步批处理 异步批处理
定时器/原生事件 同步立即更新 异步批处理
多次setState 部分合并 自动批处理

React 18通过自动批处理机制显著减少了不必要的渲染,提高了性能。

3 setState原理与源码分析

3.1 核心流程概述

setState调用后会触发一系列的更新流程,主要包括以下几个阶段:

  1. 更新调度:setState调用创建更新对象,并加入到更新队列中,然后发起调度请求。
  2. 协调阶段(Render Phase):React计算新的虚拟DOM树,与旧的树进行对比(diff算法),找出需要更新的部分。
  3. 提交阶段(Commit Phase):将协调阶段确定的变更应用到真实DOM上。
graph LR A[setState调用] --> B[创建Update对象] B --> C[加入Fiber更新队列] C --> D[调度更新] D --> E[协调阶段: 计算新状态和虚拟DOM] E --> F[提交阶段: 更新DOM] F --> G[执行回调函数]

3.2 源码关键函数分析

3.2.1 setState入口

setState方法位于ReactBaseClasses.js中:

javascript 复制代码
Component.prototype.setState = function(partialState, callback) {
  // 将更新和回调放入队列
  this.updater.enqueueSetState(this, partialState, callback, 'setState');
};

3.2.2 更新入队(enqueueSetState)

enqueueSetState函数负责创建更新对象并将其加入队列:

javascript 复制代码
enqueueSetState: function(inst, payload, callback) {
  const fiber = getInstance(inst); // 获取对应的Fiber节点
  const eventTime = requestEventTime();
  const lane = requestUpdateLane(fiber); // 获取更新优先级
  
  // 创建更新对象
  const update = createUpdate(eventTime, lane);
  update.payload = payload;
  if (callback !== undefined && callback !== null) {
    update.callback = callback;
  }
  
  // 将更新对象加入Fiber节点的更新队列
  enqueueUpdate(fiber, update);
  // 调度更新
  scheduleUpdateOnFiber(fiber, lane, eventTime);
}

3.2.3 处理更新队列(processUpdateQueue)

在渲染阶段,React会处理更新队列并计算新状态:

javascript 复制代码
function processUpdateQueue(workInProgress, props, instance, renderLanes) {
  const queue = workInProgress.updateQueue;
  let newBaseState = queue.baseState;
  let newFirstBaseUpdate = null;
  
  // 遍历更新队列,计算新状态
  let update = queue.firstUpdate;
  while (update !== null) {
    const nextUpdate = update.next;
    
    // 处理更新对象,计算新状态
    if (update.payload !== null) {
      // 函数式更新
      if (typeof update.payload === 'function') {
        const nextState = update.payload.call(instance, newBaseState, props);
        newBaseState = Object.assign({}, newBaseState, nextState);
      } else {
        // 对象式更新
        newBaseState = Object.assign({}, newBaseState, update.payload);
      }
    }
    
    update = nextUpdate;
  }
  
  // 更新Memoized状态
  workInProgress.memoizedState = newBaseState;
}

3.3 状态合并机制

React使用浅合并策略来合并状态更新:

javascript 复制代码
// 假设初始状态为 {a: 1, b: 2}
this.setState({b: 3});
this.setState({c: 4});

// 合并后的状态为 {a: 1, b: 3, c: 4}

合并过程使用Object.assign()实现:

javascript 复制代码
newState = Object.assign({}, previousState, partialState);

3.4 异步批处理机制

React 18通过优先级调度双缓冲机制实现高效的批处理:

  • 优先级调度:React使用车道模型(Lane Model)为不同更新分配优先级,高优先级更新可以打断低优先级更新。
  • 双缓冲机制:React同时维护两颗Fiber树(当前树和工作树),避免直接操作正在渲染的树。

4 特殊情况处理

4.1 在异步代码中的setState

即使在异步代码(如setTimeout)中,React 18也会批处理setState调用:

javascript 复制代码
handleClick = () => {
  // 在React事件处理函数中
  this.setState({ count: this.state.count + 1 });
  
  setTimeout(() => {
    // 在setTimeout中,React 18也会批处理
    this.setState({ count: this.state.count + 1 });
    this.setState({ count: this.state.count + 1 });
  }, 0);
};
// 最终count只会增加1,而不是3(因为批处理)

4.2 强制同步更新

在某些特殊情况下,可以使用flushSync强制同步更新:

javascript 复制代码
import { flushSync } from 'react-dom';

handleClick = () => {
  // 默认批处理
  this.setState({ count: this.state.count + 1 });
  
  // 强制同步更新
  flushSync(() => {
    this.setState({ count: this.state.count + 1 });
  });
  
  // 这里可以获取到同步更新后的值
  console.log(this.state.count); 
};

5 性能优化与最佳实践

5.1 避免不必要的渲染

  • 使用shouldComponentUpdatePureComponent避免不必要的重新渲染
  • 使用React.memo缓存函数组件

5.2 状态更新优化技巧

  1. 函数式更新:当新状态依赖旧状态时,使用函数式更新
javascript 复制代码
// 推荐:使用函数式更新
this.setState((prevState) => ({
  count: prevState.count + 1
}));

// 不推荐:直接依赖this.state
this.setState({
  count: this.state.count + 1
});
  1. 合理使用回调函数:在回调函数中处理更新后的逻辑
javascript 复制代码
this.setState(
  { count: 10 },
  () => {
    console.log('更新完成', this.state.count);
    // 执行其他依赖新状态的操作
  }
);
  1. 避免深层状态嵌套:使用浅拷贝更新深层状态
javascript 复制代码
// 更新深层状态
this.setState(prevState => ({
  user: {
    ...prevState.user,
    profile: {
      ...prevState.user.profile,
      age: 25
    }
  }
}));

总结

React 18中的setState通过自动批处理机制优先级调度,显著提高了性能表现。其核心原理是基于Fiber架构和双缓冲机制,通过创建更新对象、加入更新队列、调度更新和处理更新队列等步骤实现状态更新。

关键要点:

  • React 18中setState在任何情况下都是异步的,且会自动批处理。
  • setState通过浅合并 更新状态,使用函数式更新确保基于最新状态计算。
  • 更新流程分为调度阶段协调阶段提交阶段
  • 使用回调函数生命周期方法获取更新后的状态值。

理解setState的内部机制有助于编写更高效、可靠的React应用程序,避免常见的状态管理陷阱。

相关推荐
阿虎儿2 小时前
React 引用(Ref)完全指南
前端·javascript·react.js
阿虎儿2 小时前
React 事件类型完全指南:深入理解合成事件系统
前端·javascript·react.js
golang学习记3 小时前
🔥 从0死磕全栈:以React为起点,开启我的全栈之旅
前端·react.js
Fez7113 小时前
Momentjs对象可变性导致DatePicker异常(React+Antd)
react.js·ant design
浩星7 小时前
react+taro打包到不同小程序
react.js·小程序·taro
秋秋小事10 小时前
React Hooks useEffect的使用
react.js
北海几经夏18 小时前
React自定义Hook
前端·react.js
sorryhc20 小时前
【AI解读源码系列】ant design mobile——Space间距
前端·javascript·react.js
林太白21 小时前
Zustand状态库(简洁、强大、易用的React状态管理工具)
前端·javascript·react.js