react有多种状态更新的触发方式,譬如类组件中的setState,函数组件中的useState,那么在调度前,react在状态更新时究竟做了什么呢 ?
一、触发状态更新的方式
- ReactDOM.render —— HostRoot
- this.setState —— ClassComponent
- this.forceUpdate —— ClassComponent
- useState —— FunctionComponent
从上面可以看到react有三种触发更新的方式,三种方式的不同体现在保存状态的数据结构上,但它们的状态更新机制和流程是大体一样的。
二、状态更新的数据结构
每次状态更新会创建一个保存状态相关内容的update对象,不同更新方式创建的update对象有所不同,总共分为两类:
- ClassComponent与HostRoot使用一种update结构
- FunctionComponent单独使用一种update结构
ClassComponent与HostRoot的update结构
const update: Update<*> = {
eventTime,
lane,
suspenseConfig,
tag: UpdateState,
payload: null,
callback: null,
next: null,
};
- eventTime:任务时间,通过
performance.now()获取的毫秒数。由于该字段在未来会重构,当前我们不需要理解他。 - lane:优先级相关字段。当前还不需要掌握他,只需要知道不同
Update优先级可能是不同的。
你可以将lane类比心智模型中需求的紧急程度。
- suspenseConfig:
Suspense相关,暂不关注。 - tag:更新的类型,包括
UpdateState|ReplaceState|ForceUpdate|CaptureUpdate。 - payload:更新挂载的数据,不同类型组件挂载的数据不同。对于
ClassComponent,payload为this.setState的第一个传参。对于HostRoot,payload为ReactDOM.render的第一个传参。 - callback:更新的回调函数,在
commit阶段的layout子阶段执行。 - next:与其他
Update连接形成链表。
三、状态的批量更新
当一次任务中触发多次状态更新时,react会将多个更新合并为一次渲染,这时候会有多个update,不同的update之间如何关联呢?
答案是:将每次更新的update加入到fiber.updateQueue对象中,update之间使用单向环状链表连接。
三种updateQueue分别是:
- HostComponent
- ClassComponent与HostRoot
- FunctionComponent
HostComponent中props的更新
HostComponent的更新方式为在completeWork阶段对比新旧fiber上的props,被处理完的props会被赋值给workInProgress.updateQueue
const updatePayload = prepareUpdate(
instance,
type,
oldProps,
newProps,
rootContainerInstance,
currentHostContext,
);
//updatePayload === updateQueue
workInProgress.updateQueue = (updatePayload: any);
其中updatePayload在存在props差异时返回的结果为数组形式,他的偶数索引的值为变化的prop key,奇数索引的值为变化的prop value,并且updatePayload等于updateQueue 。
//获取updatePayload的例子
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
style: null
};
this.onChange = this.onChange.bind(this);
}
onChange() {
this.setState({
style: {
backgroundColor: "red"
}
});
}
render() {
return (
<div style={this.state.style} onClick={this.onChange}>
HELLO
</div>
);
}
}
上面代码中updateQueue的返回值:['style',null,'style',{backgroundColor:'red'}]
ClassComponent与HostRoot使用的UpdateQueue结构
const queue: UpdateQueue<State> = {
baseState: fiber.memoizedState,
firstBaseUpdate: null,
lastBaseUpdate: null,
shared: {
pending: null,
},
effects: null,
};
- baseState:本次更新前该
Fiber节点的state,Update基于该state计算更新后的state。
你可以将baseState类比心智模型中的master分支。
firstBaseUpdate与lastBaseUpdate:本次更新前该Fiber节点已保存的Update。以链表形式存在,链表头为firstBaseUpdate,链表尾为lastBaseUpdate。之所以在更新产生前该Fiber节点内就存在Update,是由于某些Update优先级较低所以在上次render阶段由Update计算state时被跳过。
你可以将baseUpdate类比心智模型中执行git rebase基于的commit(节点D)。
shared.pending:触发更新时,产生的Update会保存在shared.pending中形成单向环状链表。当由Update计算state时这个环会被剪开并连接在lastBaseUpdate后面。
你可以将shared.pending类比心智模型中本次需要提交的commit(节点ABC)。
- effects:数组。保存
update.callback !== null的Update
四、类组件的状态更新流程
this.setState内会调用this.updater.enqueueSetState方法。
Component.prototype.setState = function (partialState, callback) {
......
this.updater.enqueueSetState(this, partialState, callback, 'setState');
};
enqueueSetState方法创建update,并且将update插入updateQueue,开始调度。
enqueueSetState(inst, payload, callback) {
// 通过组件实例获取对应fiber
const fiber = getInstance(inst);
const eventTime = requestEventTime();
const suspenseConfig = requestCurrentSuspenseConfig();
// 获取优先级
const lane = requestUpdateLane(fiber, suspenseConfig);
// 创建update
const update = createUpdate(eventTime, lane, suspenseConfig);
update.payload = payload;
// 赋值回调函数
if (callback !== undefined && callback !== null) {
update.callback = callback;
}
// 将update插入updateQueue
enqueueUpdate(fiber, update);
// 调度update
scheduleUpdateOnFiber(fiber, lane, eventTime);
}