demo继续用上次那个按钮
function App() {
let [val, setVal] = useState(0)
function handle(){
setVal(val + 1)
}
return (
<div className="App">
<button onClick={() =>{handle()}}>{val}</button>
</div>
);
}
插入update
点击按钮后会触发一次更新, 由createUpdate创建一个update对象,明确一下update的结构
var update = {
eventTime: eventTime, //update发生的时间,会涉及到优先级
lane: lane, // 该update的优先级
tag: UpdateState,
payload: null, // 传入的state
callback: null,
next: null //环形链表
};
enqueueUpdate将创建的update插入到fiber.updateQueue上,看一下updateQueue的结构
{
baseState: fiber.memoizedState,
firstBaseUpdate: null,
lastBaseUpdate: null,
shared: {
pending: null,
},
effects: null
}
baseState: 由第一个被跳过的update之间的update计算后得出的状态。在每一轮更新中,如果update优先级不够(没过期)可能会被跳过到后面的更新执行。
firstBaseUpdate: 指向上次被跳过的第一个update,如果没有跳过的update就是null
lastBaseUpdate: 如果有update被跳过,lastBaseUpdate就是上次更新update链表的最后一个update
shared.pending: 本次更新update构成的环状链表(一次更新可能产生多个udpate)
插入更新的过程就是将update插入到shared.pending环状链表上,下面是插入的代码
if (pending === null) {
update.next = update;
} else {
update.next = pending.next;
pending.next = update;
}
sharedQueue.pending = update;
pending指向环状链表的最后一个update,插入的过程就是:1. 将新的update.next指向环状链表的第一个元素,也就是pending.next。2. 将之前的最后一个update.next指向新的update,现在新update就是最后一个update。3. 将pending指向新update
使用环状链表可以有很高的插入效率,如果是普通单向链表,插入新的update需要遍历到链表尾部。
根据update计算state
在插入update后会进入调度过程,在beginWork阶段会调用processUpdate进行state的计算
通过前面的过程可知,此时fiber上存在两个updateQueue,上次更新跳过的updateQueue和这次更新产生的updateQueue(firstBaseUpdate和shared.pending1)。
计算state前需要将新产生的update链接到上次剩下的updateQueue上,该过程在下面的代码实现。
//重置queue.shared.pending1
queue.shared.pending = null;
// 将lastPendingUpdate.next指向null,断开环状链表
var lastPendingUpdate = pendingQueue;
var firstPendingUpdate = lastPendingUpdate.next;
lastPendingUpdate.next = null;
//链接两个链表,将最后一个update.next指向新的链表
if (lastBaseUpdate === null) {
firstBaseUpdate = firstPendingUpdate;
} else {
lastBaseUpdate.next = firstPendingUpdate;
}
lastBaseUpdate = lastPendingUpdate;
总结一下这个过程:重置shared.pending -> 断开环状链表 -> 将新udpate链表链接到旧的update链表后面
if (current !== null) {
var currentQueue = current.updateQueue;
var currentLastBaseUpdate = currentQueue.lastBaseUpdate;
if (currentLastBaseUpdate !== lastBaseUpdate) {
if (currentLastBaseUpdate === null) {
currentQueue.firstBaseUpdate = firstPendingUpdate;
} else {
currentLastBaseUpdate.next = firstPendingUpdate;
}
currentQueue.lastBaseUpdate = lastPendingUpdate;
}
}
这里current fiber进行了相同的操作,参考其他的博客,这么做的意义是将updateQueue保存在current fiber上,当update被打断时防止更新丢失?关于为什么会打断更新后面会仔细写一下。
下面就涉及到了更新跳过的情况,首先看一下声明的几个变量
var newState = queue.baseState; // 本次state计算将基于newState
var newLanes = NoLanes;
var newBaseState = null; //如果有update被跳过,newBaseState由第一个被跳过的update之前的update计算得出,作为下一次更新的baseState。
var newFirstBaseUpdate = null;
var newLastBaseUpdate = null;
var update = firstBaseUpdate;
看一下如何处理更新,处理过程的框架是一个do while循环
var update = firstBaseUpdate;
do{
//something
}while(true)
看一下do while里面的内容
if (!isSubsetOfLanes(renderLanes, updateLane)) {
var clone = {
eventTime: updateEv5entTime,
lane: updateLane,
tag: update.tag,
payload: update.payload,
callback: update.callback,
next: null
};
if (newLastBaseUpdate === null) {
newFirstBaseUpdate = newLastBaseUpdate = clone;
newBaseState = newState;
} else {
newLastBaseUpdate = newLastBaseUpdate.next = clone;
}
newLanes = mergeLanes(newLanes, updateLane);
}
这个if里面检测一下当前update的优先级是否在本次渲染的优先级内,如果优先级低则跳过updata。跳过时将其添加到newFirstBaseUpdate内(下次更新的baseUpdate链表),而且要将newState保存在newBaseState内, 所以说baseState保存的是第一个被跳过的update之前的update计算出的state。
else {
if (newLastBaseUpdate !== null) {
var _clone = {
eventTime: updateEventTime,
lane: NoLane,
tag: update.tag,
payload: update.payload,
callback: update.callback,
next: null
};
newLastBaseUpdate = newLastBaseUpdate.next = _clone;
}
newState = getStateFromUpdate(workInProgress, queue, update, newState, props, instance);
var callback = update.callback;
if (callback !== null) {
workInProgress.flags |= Callback;
var effects = queue.effects;
if (effects === null) {
queue.effects = [update];
} else {
effects.push(update);
}
}
}
进入当前这个else说明update优先级足够,先看if (newLastBaseUpdate !== null)的情况,此时说明已经有低优先级的update被跳过了,那就应该将该update添加到newLastBaseUpdate后(lastBaseUpdate保存的是从第一个被跳过的update开始的update链表)。getStateFromUpdate会根据update及state计算出新的state(update种类不同进入不同的case,比较常见的就是UpdateState)
_assign({}, prevState, partialState);
/**
*
* 这个就是普通UpdateState的合并方法,通过assign合并对象,所以调用setState时即使没有传入之前的state,旧的state也不会丢失。
*
* */
看一下while的最后一部分
if (update === null) {
pendingQueue = queue.shared.pending;
if (pendingQueue === null) {
break;
} else {
var _lastPendingUpdate = pendingQueue;
var _firstPendingUpdate = _lastPendingUpdate.next;
_lastPendingUpdate.next = null;
update = _firstPendingUpdate;
queue.lastBaseUpdate = _lastPendingUpdate;
queue.shared.pending = null;
}
}
处理update时有可能产生新的update(比如typeof payload === ‘function’),所以这里检查一下queue.shared.pending, 如果产生新的update,添加到lastBaseUpdate上,重复上面的操作。
if (newLastBaseUpdate === null) {
newBaseState = newState;
}
queue.baseState = newBaseState;
queue.firstBaseUpdate = newFirstBaseUpdate;
queue.lastBaseUpdate = newLastBaseUpdate;
markSkippedUpdateLanes(newLanes);
workInProgress.lanes = newLanes;
workInProgress.memoizedState = newState;
这里是更新计算的最后阶段,主要就是挂载被跳过的更新,将第一个被跳过的update之前的update计算出的newBaseState放在baseState上,将正常计算出的newState放在memoizedState上。后面计算state还是要基于baseState,为了保证跳过update不会影响最终的state。