技术/前端/React
面试中经常碰到的一个问题就是setState
之后发生了什么?这个看似简单,其实可以挖的很深,看下面:
setState两个基本特性
1 setState是异步的,react通常会集齐一些组件后一起更新组件,以保证性能。所以当我们在设置setState后,立即通过this.state是获取最新状态是获取不到的。如果要获取最新的状态可以在setState回调中获取。
this.setState({name: 'laodao'},()=>{})
2 setState进行多次执行,在react执行合并多次为一次情况下,相当于后面覆盖前面,相当于加一次。
Object.assign(
previousState,
{quantity: this.state.quantity + 1},
{quantity: this.state.quantity + 1}
)
setState执行过程
setState 在 ReactBaseClasses.js里面
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对象,或者是函数。
第二个参数是修改之后的回调函数。
调用 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都会更新组件~。
这段代码就解释了我们常常听说的: setState是一个异步的过程,它会集齐一批需要更新的组件然后一起更新 。
而batchingStrategy 又是个什么东西呢?
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
/**
*
<pre>
* wrappers (injected at creation time)
* + +
* | |
* +-----------------|--------|--------------+
* | v | |
* | +---------------+ | |
* | +--| wrapper1 |---|----+ |
* | | +---------------+ v | |
* | | +-------------+ | |
* | | +----| wrapper2 |--------+ |
* | | | +-------------+ | | |
* | | | | | |
* | v v v v | wrapper
* | +---+ +---+ +---------+ +---+ +---+ | invariants
* perform(anyMethod) | | | | | | | | | | | | maintained
* +----------------->|-|---|-|---|-->|anyMethod|---|---|-|---|-|-------->
* | | | | | | | | | | | |
* | | | | | | | | | | | |
* | | | | | | | | | | | |
* | +---+ +---+ +---------+ +---+ +---+ |
* | initialize close |
* +-----------------------------------------+
* </pre>
*/
简单说明一下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 最后实现组件的更新。
[image:00C17BF8-01AE-4394-B9D1-8A7EC8BA769C-488-0000B560DE0CA5EF/v2-fc21554110ae7147a5e01c7db6f3cf4f_b.jpg]
[image:0F6EE429-D91F-4A9F-B57B-662903A52041-488-0000B560DDE95C24/v2-fc21554110ae7147a5e01c7db6f3cf4f_hd.jpg]
setState
的异步特性
首先setState
只有在生命周期和合成事件回调中才会有所谓的异步效果,才会进入上面说的批量更新的逻辑中:
dispatchEvent: **function** (topLevelType, nativeEvent) {
// disable了则直接不回调相关方法
**if** (!ReactEventListener._enabled) {
**return**;
}
**var** bookKeeping = TopLevelCallbackBookKeeping.getPooled(topLevelType, nativeEvent);
**try** {
// 放入
ReactUpdates.batchedUpdates(handleTopLevelImpl, bookKeeping);
} **finally** {
TopLevelCallbackBookKeeping.release(bookKeeping);
}
}
而在原生事件中或定时任务里,由于没有触发事务操作即ReactDefaultBatchingStrategy
中的batchUpdates
方法,无法修改isBatchingUpdates
变量,所以会直接修改state,以及触发组件更新