React - setState 原理

一、setState 的基本特性

1. setState 获取更新状态

在 React 控制的回调函数中,setState是异步的,所以在设置setState后,是无法立即通过this.state是获取最新状态的。如果要获取最新的状态得在setState回调中获取。

this.setState({
	name: 'test'
}, ()=>{
	console.log(this.state.name);
})

2. setState 合并调用

对象式setState多次调用会发生合并,就像Object.assign的对象合并,相同的key最后一个会覆盖前面的key。

this.setState({
	number: this.state.number + 1
})
this.setState({
	number: this.state.number + 1
})

// 类似于
Object.assign(
  previousState,
  {number: this.state.number + 1},
  {number: this.state.number + 1}
)

// 只执行一次
{ ...previousState, number: this.state.number + 1 }

3. setState 同步更新

setstate在原生事件,setTimeout,setInterval,promise等异步操作中,state会同步更新。

当执行到 setTimeout 的时候,会将函数块放入宏任务列队里暂不执行,先去执行主进程代码块,等主进程执行完了, isBatchingUpdates 变为了 false ,导致最后去执行队列里的 setState 时候, 表现就会和原生事件一样,同步拿到最新的state的值。

4. setState 异步批量更新

在react生命周期和合成事件执行前后都有相应的钩子,分别是pre钩子和post钩子,pre钩子会调用batchedUpdate方法将isBatchingUpdates变量置为true,开启批量更新,而post钩子会将isBatchingUpdates置为false。

二、setState 执行过程

setstate方法

ReactComponent.prototype.setState = function(partialState, callback) {
  invariant(
    typeof partialState === 'object' ||
      typeof partialState === 'function' ||
      partialState == null,
    'setState(...): takes an object of state variables to update or a ' +
      'function which returns an object of state variables.',
  );
  // 这里的this.updater就是ReactUpdateQueue
  this.updater.enqueueSetState(this, partialState);
  if (callback) {
    this.updater.enqueueCallback(this, callback, 'setState');
  }
};

setState函数有两个参数:

第一个参数是需要修改的setState对象,或者是函数。

第二个参数是修改之后的回调函数。

这里的partialState会产生新的state以一种Object.assgine()的方式跟旧的state进行合并。

enqueueSetState

  enqueueSetState: function (publicInstance, partialState) {
     // 获取当前组件的instance
    var internalInstance = getInternalInstanceReadyForUpdate(publicInstance, 'setState');

     // 将要更新的state放入一个数组里
     // _pendingStateQueue(待更新队列) 与 _pendingCallbacks(更新回调队列)
     var queue = internalInstance._pendingStateQueue || (internalInstance._pendingStateQueue = []);
    queue.push(partialState);

     //  将要更新的component instance也放在一个队列里
    enqueueUpdate(internalInstance);
  }

enqueueSetState 主要任务:
1、将新的state放进数组里
2、用enqueueUpdate来处理将要更新的实例对象

enqueueUpdate

function enqueueUpdate(component) {
  // 如果没有处于批量创建/更新组件的阶段,则处理update state事务
  if (!batchingStrategy.isBatchingUpdates) {
    batchingStrategy.batchedUpdates(enqueueUpdate, component);
    return;
  }
  // 如果正处于批量创建/更新组件的过程,将当前的组件放在dirtyComponents数组中
  dirtyComponents.push(component);
}

由这段代码可以看到,当前如果正处于创建/更新组件的过程,就不会立刻去更新组件,而是先把当前的组件放在dirtyComponent里,所以不是每一次的setState都会更新组件。

batchingStrategy

var ReactDefaultBatchingStrategy = {
  // 用于标记当前是否出于批量更新
  isBatchingUpdates: false,
  // 当调用这个方法时,正式开始批量更新
  batchedUpdates: function (callback, a, b, c, d, e) {
    var alreadyBatchingUpdates = ReactDefaultBatchingStrategy.isBatchingUpdates;

    ReactDefaultBatchingStrategy.isBatchingUpdates = true;

    // 如果当前事务正在更新过程在中,则调用callback,既enqueueUpdate
    if (alreadyBatchingUpdates) {
      return callback(a, b, c, d, e);
    } else {
    // 否则执行更新事务
      return transaction.perform(callback, null, a, b, c, d, e);
    }
  }
};

这里注意两点:
1、如果当前事务正在更新过程中,则使用enqueueUpdate将当前组件放在dirtyComponent里。
2、如果当前不在更新过程的话,则执行更新事务。

transaction

简单说明一下transaction对象,它暴露了一个perform的方法,用来执行anyMethod,在anyMethod执行的前,需要先执行所有wrapper的initialize方法,在执行完后,要执行所有wrapper的close方法,就辣么简单。

在ReactDefaultBatchingStrategy.js,tranction 的 wrapper有两个 FLUSH_BATCHED_UPDATES, RESET_BATCHED_UPDATES

var RESET_BATCHED_UPDATES = {
  initialize: emptyFunction,
  close: function () {
    ReactDefaultBatchingStrategy.isBatchingUpdates = false;
  }
};

var FLUSH_BATCHED_UPDATES = {
  initialize: emptyFunction,
  close: ReactUpdates.flushBatchedUpdates.bind(ReactUpdates)
};

var TRANSACTION_WRAPPERS = [FLUSH_BATCHED_UPDATES, RESET_BATCHED_UPDATES]; 

这两个wrapper的initialize都没有做什么事情,但是在callback执行完之后,RESET_BATCHED_UPDATES 的作用是将isBatchingUpdates置为false, FLUSH_BATCHED_UPDATES 的作用是执行flushBatchedUpdates。

里面会循环所有dirtyComponent,调用updateComponent来执行所有的生命周期方法,componentWillReceiveProps, shouldComponentUpdate, componentWillUpdate, render, componentDidUpdate 最后实现组件的更新。

整体流程

  1. batchedUpdates 函数让 isBatchingUpdates = true
  2. 函数中调用setState
  3. setState 设置 enqueueSetState 和 enqueueCallback
  4. enqueueSetState 调用 enqueueUpdate
    待更新的state放进调用component的更新队列中
    enqueueUpdate 调用 component(待更新的component 放进一个更新 队列)
  5. enqueueUpdate
    判断isBatchingUpdates
    false 执行batchingStrategy.batchedUpdates
    true 将component放进dirtyComponents数组
  6. batchingStrategy.batchedUpdates
    将当前的 isBatchingUpdates 设置为true
    判断原先的 isBatchingUpdates 是否为true
    false 立即执行更新事务
    true 执行 enqueueUpdate
  7. 事务执行时
    isBatchingUpdates 设置为false
    循环所有dirtyComponent,调用updateComponent来执行所有的生命周期方法,componentWillReceiveProps, shouldComponentUpdate, componentWillUpdate, render, componentDidUpdate 最后实现组件的更新。

三、setState 相关的问题

1. 连续使用 setState,为什么不能实时改变

state.count = 0;
this.setState({count: state.count + 1}); 
this.setState({count: state.count + 1}); 
this.setState({count: state.count + 1}); 
// state.count === 1,不是 3

因为 this.setState 方法为会进行批处理,后调的 setState 会覆盖统一周期内先调用的 setState 的值,

2. 为什么要 setState,而不是直接改变this.state

因为 setState 做的事情不仅仅只是修改了 this.state 的值,另外最重要的是它会触发 React 的更新机制,会进行diff,然后将 patch 部分更新到真实 dom 里。

如果你直接 this.state 的话,state 的值确实会改,但是它不会驱动 React 重新渲染。

当调用 setState 后,React 的 生命周期函数 会依次顺序执行

  • static getDerivedStateFromProps
  • shouldComponentUpdate
  • render
  • getSnapshotBeforeUpdate
  • componentDidUpdate

3. 为什么会出现异步的情况

为了性能优化,React 会将每次调用的 setState 放入队列中做一次性处理,在事件结束之后再产生一次重新渲染,为的就是把 Virtual DOM 和 DOM 树操作降到最小,提高应用性能。

参考链接
React setState 之后发生了什么
React setState 整理总结
深入浅出 setState 原理篇

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
回答: React中的setState方法是用来更新组件的状态的。根据引用\[1\],setState是异步的,所以在设置setState后,无法立即通过this.state获取最新状态。如果要获取最新的状态,需要在setState的回调函数中获取。另外,根据引用\[2\],setState方法会合并调用,即多次调用setState会被合并成一次更新。在React的生命周期和合成事件执行前后都有相应的钩子,分别是pre钩子和post钩子,根据引用\[3\],pre钩子会调用batchedUpdate方法将isBatchingUpdates变量置为true,开启批量更新,而post钩子会将isBatchingUpdates置为false。这样可以提高性能,减少不必要的渲染。总的来说,setState的执行过程是将更新的状态放入队列中,然后在适当的时机进行批量更新。 #### 引用[.reference_title] - *1* *3* [React - setState 原理](https://blog.csdn.net/kelly0721/article/details/117252206)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [React解密:ReactsetState的运行原理是什么](https://blog.csdn.net/leelxp/article/details/108238766)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值