setState原理
- setState是异步更新的
setState调用时,会执行enqueueSetState方法对当前要设置的state和_pendingStateQueue更新队列进行合并,最终通过enqueueUpdate执行state更新。首先,我们看个例子,如下所示,如果你知道答案那么你对这个机制是了解的。
componentDidMount() {
console.log('App dit')
this.setState({value: this.state.value + 1})
console.log('value:', this.state.value)
this.setState({value: this.state.value + 1})
console.log('value:', this.state.value)
setTimeout(() => {
this.setState({value: this.state.value + 1})
console.log('value:', this.state.value)
this.setState({value: this.state.value + 1})
console.log('value:', this.state.value)
}, 0);
}
下面是张简单的机制流程图
1-1.png
那到此为止,我们可以知道前两次的state输出都是0了。那解释后面两state之前,我们来介绍一个概念,transaction(事务),react更新机制依赖事务,被事务所包裹。所谓事务就是一个高阶函数,用wrapper封装起来,然后通过事务提供的perform方法执行method,最后通过事务close掉。那前面两次setState的时候,我们通过setState的调用栈发现,前两次已经处于batchUpdate中了,因为React组件渲染到Dom中的过程就处于一个大事务中,然后isBatchUpdates标识为true,所以无法立即更新,而settimerout则没有这么操作,所以每次setState都会更新了,所以上面答案就是 0 0 2 3
React16最新版本setState执行过程
Component.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.enqueueSetState(this, partialState, callback, 'setState');
};
这里我们发现,还是调用enqueueSetState,接下来我们看下这个方法里面做了啥:
enqueueSetState(inst, payload, callback) {
const fiber = getInstance(inst);
const currentTime = requestCurrentTimeForUpdate();
const suspenseConfig = requestCurrentSuspenseConfig();
const expirationTime = computeExpirationForFiber(
currentTime,
fiber,
suspenseConfig,
);
const update = createUpdate(expirationTime, suspenseConfig);
update.payload = payload;
if (callback !== undefined && callback !== null) {
if (__DEV__) {
warnOnInvalidCallback(callback, 'setState');
}
update.callback = callback;
}
enqueueUpdate(fiber, update);
scheduleWork(fiber, expirationTime);
},
这里,先获取fiber实例,然后获取当前时间,这个当前时间,这个当前时间是这样算的:如果现在是处于react内,直接根据当前时间Now生成,如果是在react外(比如在浏览器原生事件内,那么这个期间发生的所有update都会被重置为同一个更新,当然时间也是一样的),然后是获取suspense配置,这个就不多说了,expirationTime根据currentTime生成,我们看下图源码,到期时间会根据当前fiber的mode或者是在渲染期间直接使用当前的渲染超时时间,或者如果配置了suspenseConfig,则根据suspenseConfig.timeoutMs生成,或者根据priorityLevel(任务优先级)生成超时。
image.png
接下来,执行createUpdate,这个主要是生成一个update对象,这个对象含有以下属性:
let update: Update<*> = {
expirationTime,
suspenseConfig,
tag: UpdateState,
payload: null,
callback: null,
next: null,
nextEffect: null,
};
然后执行enqueueUpdate,我们看下面源码,这里我们先获取alternate,这个参数是判断当前是否是一个fiber,那这个方法主要是初始化queue1和queue2,然后加入到appendUpdateToQueue
export function enqueueUpdate<State>(fiber: Fiber, update: Update<State>) {
// Update queues are created lazily.
const alternate = fiber.alternate;
let queue1;
let queue2;
if (alternate === null) {
// There's only one fiber.
queue1 = fiber.updateQueue;
queue2 = null;
if (queue1 === null) {
queue1 = fiber.updateQueue = createUpdateQueue(fiber.memoizedState);
}
} else {
// There are two owners.
queue1 = fiber.updateQueue;
queue2 = alternate.updateQueue;
if (queue1 === null) {
if (queue2 === null) {
// Neither fiber has an update queue. Create new ones.
queue1 = fiber.updateQueue = createUpdateQueue(fiber.memoizedState);
queue2 = alternate.updateQueue = createUpdateQueue(
alternate.memoizedState,
);
} else {
// Only one fiber has an update queue. Clone to create a new one.
queue1 = fiber.updateQueue = cloneUpdateQueue(queue2);
}
} else {
if (queue2 === null) {
// Only one fiber has an update queue. Clone to create a new one.
queue2 = alternate.updateQueue = cloneUpdateQueue(queue1);
} else {
// Both owners have an update queue.
}
}
}
if (queue2 === null || queue1 === queue2) {
// There's only a single queue.
appendUpdateToQueue(queue1, update);
} else {
// There are two queues. We need to append the update to both queues,
// while accounting for the persistent structure of the list — we don't
// want the same update to be added multiple times.
if (queue1.lastUpdate === null || queue2.lastUpdate === null) {
// One of the queues is not empty. We must add the update to both queues.
appendUpdateToQueue(queue1, update);
appendUpdateToQueue(queue2, update);
} else {
// Both queues are non-empty. The last update is the same in both lists,
// because of structural sharing. So, only append to one of the lists.
appendUpdateToQueue(queue1, update);
// But we still need to update the `lastUpdate` pointer of queue2.
queue2.lastUpdate = update;
}
}
if (__DEV__) {
if (
fiber.tag === ClassComponent &&
(currentlyProcessingQueue === queue1 ||
(queue2 !== null && currentlyProcessingQueue === queue2)) &&
!didWarnUpdateInsideUpdate
) {
warningWithoutStack(
false,
'An update (setState, replaceState, or forceUpdate) was scheduled ' +
'from inside an update function. Update functions should be pure, ' +
'with zero side-effects. Consider using componentDidUpdate or a ' +
'callback.',
);
didWarnUpdateInsideUpdate = true;
}
}
}
下面是appendUpdateToQueue,如果队列是空的,则queue队列的队头和队尾指针指向当前update,否则队尾指针指向当前的update。
function appendUpdateToQueue<State>(
queue: UpdateQueue<State>,
update: Update<State>,
) {
// Append the update to the end of the list.
if (queue.lastUpdate === null) {
// Queue is empty
queue.firstUpdate = queue.lastUpdate = update;
} else {
queue.lastUpdate.next = update;
queue.lastUpdate = update;
}
}
最后我们回到主函数,执行scheduleWork
export function scheduleUpdateOnFiber(
fiber: Fiber,
expirationTime: ExpirationTime,
) {
//检测是否嵌套超过最大深度,如果超过就报"超过最大更新深度。
// 当组件在componentWillUpdate或componentdiddupdate内重复调用setState时,可能会发生这种情况。
// React限制嵌套更新的数量以防止无限循环。超过最大更新深度。当组件在useffect内部调用setState时,
// 可能会发生这种情况,但useffect要么没有依赖数组,要么在每个呈现上都有一个依赖项更改"
checkForNestedUpdates();
warnAboutInvalidUpdatesOnClassComponentsInDEV(fiber);
// 查找要更新的fiber所依赖的Root,如果没有找到,则说明是处于卸载的组件里面。
const root = markUpdateTimeFromFiberToRoot(fiber, expirationTime);
if (root === null) {
warnAboutUpdateOnUnmountedFiberInDEV(fiber);
return;
}
// 检测是否接受本次更新,如果更新时间小于渲染时间则终止,或者当前正在执行的root为空也终止
checkForInterruption(fiber, expirationTime);
recordScheduleUpdate();
// 获取当前的任务优先级别
const priorityLevel = getCurrentPriorityLevel();
// 如果超时时间是sync
if (expirationTime === Sync) {
if (
// Check if we're inside unbatchedUpdates 检查我们是否在未锁定的更新中
(executionContext & LegacyUnbatchedContext) !== NoContext &&
// Check if we're not already rendering 检查我们是否还没有渲染
(executionContext & (RenderContext | CommitContext)) === NoContext
) {
// 如果上述条件都符合,则在更新前要把当前挂起的交互数据进行保存
schedulePendingInteractions(root, expirationTime);
// This is a legacy edge case. The initial mount of a ReactDOM.render-ed
// root inside of batchedUpdates should be synchronous, but layout updates
// should be deferred until the end of the batch.
//执行更新
performSyncWorkOnRoot(root);
} else {
//同步更新是立即执行的,除非我们已经在锁定更新中,如果是,这是跟传统方式一样的,放入同步会掉队列,等待当前交互完成
ensureRootIsScheduled(root);
schedulePendingInteractions(root, expirationTime);
if (executionContext === NoContext) {
flushSyncCallbackQueue();
}
}
} else {
// 使用此函数可为root安排任务。每个root只有一个任务;如果任务已被调度,
// 我们将检查以确保现有任务的到期时间与root所工作的下一级的到期时间相同。
// 这个函数在每次更新之前调用,并且在退出任务之前就被调用。
ensureRootIsScheduled(root);
// 在更新前要把当前挂起的交互数据进行保存
schedulePendingInteractions(root, expirationTime);
}
if (
(executionContext & DiscreteEventContext) !== NoContext &&
// 仅考虑用户阻塞优先级或更高级别的更新
// discrete, even inside a discrete event.
(priorityLevel === UserBlockingPriority ||
priorityLevel === ImmediatePriority)
) {
// This is the result of a discrete event. Track the lowest priority
// discrete update per root so we can flush them early, if needed.
if (rootsWithPendingDiscreteUpdates === null) {
rootsWithPendingDiscreteUpdates = new Map([[root, expirationTime]]);
} else {
const lastDiscreteTime = rootsWithPendingDiscreteUpdates.get(root);
if (lastDiscreteTime === undefined || lastDiscreteTime > expirationTime) {
rootsWithPendingDiscreteUpdates.set(root, expirationTime);
}
}
}
}
最后我们总结一下
react16 setState机制.png