分析画的思维脑图,点击下载
目前对setState的基本认识:
1.setState异步更新数据,想要获取数据从回调函数中获取,
2.多次setState函数调用会合并执行
基于以上的认知,有这样几个问题:
1.setState 真的是异步吗?如果不是异步,同步与异步在什么场景下区分?
2.setState 如何对多次调用进行合并 源码注解和思维脑图
setState执行流程:
1.react
Component.prototype.setState = function (partialState, callback) {
if (!(typeof partialState === 'object' || typeof partialState === 'function' || partialState == null)) {
{
throw Error("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');
};
2.react-dom
/*
状态更新
var UpdateState = 0;// 更新状态
var ReplaceState = 1;// 替换状态
var ForceUpdate = 2; // 强制更新
var CaptureUpdate = 3;// 捕获更新
*/
var classComponentUpdater = {
isMounted: isMounted,
enqueueSetState: function (inst, payload, callback) {
// 获取fiber对象
var fiber = get(inst);
var currentTime = requestCurrentTimeForUpdate();
var suspenseConfig = requestCurrentSuspenseConfig();
var expirationTime = computeExpirationForFiber(currentTime, fiber, suspenseConfig);
var update = createUpdate(expirationTime, suspenseConfig);
update.payload = payload;
//判断setState是有存在回调函数,如果存在的话赋值给更新队列的回调函数
if (callback !== undefined && callback !== null) {
{
warnOnInvalidCallback$1(callback, 'setState');
}
update.callback = callback;
}
enqueueUpdate(fiber, update);
scheduleWork(fiber, expirationTime);
},
};
react 有几种更新方式,这里只有setState的相关代码
enqueueSetState执行会生成 fiber,update和expirationTime,
以下为例
componentDidMount() {
this.setState({
testNum:3
})
this.setState({
testNum:5
})
}
生命周期中两次调用setState,react 会将更新队列合并为链表(tree结构),所以最终的更新内容是{testNum: 5}
updateQueue:
baseState:
testNum: 0
testNum1: 0
__proto__: Object
firstCapturedEffect: null
firstCapturedUpdate: null
firstEffect: null
firstUpdate:
callback: null //回调函数
expirationTime: 1073741823 //过期时间
next: //下一个更新
callback: null
expirationTime: 1073741823
next: null //如果还有setState还会往下链
nextEffect: null
payload: {testNum: 5} //更新内容
priority: 99 // 优先级
suspenseConfig: null
tag: 0 //0更新 1替换 2强制更新 3捕获性的更新
nextEffect: null
payload: {testNum: 3}
priority: 99
suspenseConfig: null
tag: 0
lastCapturedEffect: null //队列中第一个捕获类型的update
lastCapturedUpdate: null //队列中最后一个捕获类型的update
lastEffect: null //第一个side effect
lastUpdate: //最后一个side effect
callback: null //回调函数
expirationTime: 1073741823 //过期时间
next: null
nextEffect: null
payload: {testNum: 5} //更新内容
priority: 99 // 优先级
suspenseConfig: null
tag: 0 //0更新 1替换 2强制更新 3捕获性的更新
scheduleWork函数会执行异步走 ensureRootIsScheduled函数执行渲染流程
function scheduleUpdateOnFiber(fiber, expirationTime) {
//判断是否是无限循环update
// 函数 3-1-7-1
checkForNestedUpdates();
//测试环境用
// 函数 3-1-7-2
warnAboutInvalidUpdatesOnClassComponentsInDEV(fiber);
//找到rootFiber并遍历更新子节点的expirationTime
// 函数 3-1-7-3
var root = markUpdateTimeFromFiberToRoot(fiber, expirationTime);
if (root === null) {
// 函数 3-1-7-4
warnAboutUpdateOnUnmountedFiberInDEV(fiber);
return;
}
//判断是否有高优先级任务打断当前正在执行的任务
// 函数 3-1-7-5
checkForInterruption(fiber, expirationTime);
//报告调度更新,测试环境用的
recordScheduleUpdate();
var priorityLevel = getCurrentPriorityLevel();
//如果expirationTime等于最大整型值的话
//如果是同步任务的过期时间的话
if (expirationTime === Sync) {
if ( //检查一下我们是否在非batchedupdates里
(executionContext & LegacyUnbatchedContext) !== NoContext && // 检查我们是否已经渲染
(executionContext & (RenderContext | CommitContext)) === NoContext) {
// 在根上注册挂起的交互,以避免丢失跟踪的交互数据。
// 函数 3-1-7-6
schedulePendingInteractions(root, expirationTime);
//这是一个遗留的边缘案例。反应物的初始量
// batchedupdate的根目录应该是同步的,但是布局要更新
//应该推迟到这批货的末尾。
// 函数 3-1-7-7
performSyncWorkOnRoot(root);
} else {
// 函数 3-1-7-8
ensureRootIsScheduled(root);
//立即执行调度任务
schedulePendingInteractions(root, expirationTime);
if (executionContext === NoContext) {
//现在刷新同步工作,除非我们已经在工作或内部
//一批。这是故意在scheduleUpdateOnFiber而不是
// scheduleCallbackForFiber来保留调度回调的能力
//不立即冲洗。我们只对用户发起这样做
//刷新同步任务队列
// 函数 3-1-7-9
flushSyncCallbackQueue();
}
}
} else {
//如果是异步任务的话,则立即执行调度任务
ensureRootIsScheduled(root);
schedulePendingInteractions(root, expirationTime);
}
if ((executionContext & DiscreteEventContext) !== NoContext && ( // Only updates at user-blocking priority or greater are considered
//离散的,即使在离散事件中。
priorityLevel === UserBlockingPriority$2 || priorityLevel === ImmediatePriority)) {
//这是一个离散事件的结果。跟踪最低优先级
//每个根的独立更新,这样我们就可以在需要的时候尽早清除它们。
if (rootsWithPendingDiscreteUpdates === null) {
rootsWithPendingDiscreteUpdates = new Map([[root, expirationTime]]);
} else {
var lastDiscreteTime = rootsWithPendingDiscreteUpdates.get(root);
if (lastDiscreteTime === undefined || lastDiscreteTime > expirationTime) {
rootsWithPendingDiscreteUpdates.set(root, expirationTime);
}
}
}
}
对于setState的问题
1.setState是否都是异步?如果不是异步,同步与异步在什么场景下区分?
答案:在组件生命周期或React合成事件中,setState是异步;在setTimeout或者原生dom事件中,setState是同步,那么问题来了:相关连接
为什么是异步?
1.保证内部的一致性
即使state同步更新,props也不会。(props直到重新渲染父组件,然后同步执行,批处理才会显示在窗口之外。)
2.启用并发更新
2.setState如何合并
这个问题在setState流程中已经说明
本身以前在工作中很少用setState管理数据,基本都在redux中去统一管理,setState自身的异步状态容易掉坑,虽然可以回调解决,看完源码,setState虽然能合并同一上下文环境多次调用,但队列合并前的流程是没有合并的,还需要多次执行,个人觉得还是慎用。对于react16.12的源码,真心感觉自己太low,好多地方看不明白,暂时记录到这,后期在补充