setState
调用过程
setState->this.updater.enqueueSetState->scheduleWork->requestWork
- enqueueSetState
const classComponentUpdater = {
...
enqueueSetState(inst, payload, callback) {
// 获取当前组件的instance
var internalInstance = getInternalInstanceReadyForUpdate(publicInstance, 'setState');
// 将要更新的state放入一个数组里
var queue = internalInstance._pendingStateQueue || (internalInstance._pendingStateQueue = []);
queue.push(partialState);
// 关键代码
enqueueUpdate(fiber, update); // 原生事件,因为
scheduleWork(fiber, expirationTime);
},
...
- enqueueUpdate
function enqueueUpdate(component) {
if (!batchingStrategy.isBatchingUpdates) {
batchingStrategy.batchedUpdates(enqueueUpdate, component);
return;
}
dirtyComponents.push(component);
}
- batchedUpdates
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);
}
}
};
- scheduleWork
if (
!isWorking ||
isCommitting ||
nextRoot !== root
) {
const rootExpirationTime = root.expirationTime;
// 关键执行requestWork
requestWork(root, rootExpirationTime);
}
- requestWork
function requestWork(root, expirationTime) {
// 将根节点添加到调度任务中
addRootToSchedule(root, expirationTime)
// 第一步,isRendering在生命周期中为true,在合成事件和原生事件(不存在)
if (isRendering) {
return;
}
// isBatchingUpdates、isUnbatchingUpdates是全局变量
// isBatchingUpdates一开始默认为false,合成事件中会被设置为true
if (isBatchingUpdates) {
if (isUnbatchingUpdates) {
....
performWorkOnRoot(root, Sync, false);
}
return;
}
if (expirationTime === Sync) {
performSyncWork();
} else {
scheduleCallbackWithExpirationTime(root, expirationTime);
}
}
三种调方式
- 生命周期调用
- 合成事件调用
- 其他情况调用(原声事件,异步调用)
生命周期调用
- 案列一:componentdidmount
state = {
age: 0,
name: null
}
componentDidMount() {
this.setState({age:1})
console.log(this.stage.age); // 0
this.setState({name:'test'})
this.setState({age:1})
console.log(this.stage.name); // null
}
分析:
1 、在每个组件渲染之前调用batchedUpdate(),此时isBatchingUpdates由false --> true了
将 React 组件渲染到 DOM 中的过程就处于一个大的 Transaction 中
所以会直接调用batchedUpdate()
// ReactMount.js
ReactUpdates.batchedUpdates(
batchedMountComponentIntoNode, // 负责初始渲染组件
componentInstance,
container,
shouldReuseMarkup,
context,
);
为什么首次渲染组件要调用batchedUpdates?
因为组件渲染会调用生命周期,生命周期可能会调用setState
开启批量更新,存储更新,在事务结束后,去批量跟新
2 、组件初始化的时候会调用performWorkOnRoot,将isRendering = true,然后:
constructor -> getDerivedStateFromProps -> render -> componentDidMount
3、此时setState走向流程如下:
function enqueueUpdate(component) {
if (!batchingStrategy.isBatchingUpdates) {
batchingStrategy.batchedUpdates(enqueueUpdate, component);
return;
}
dirtyComponents.push(component); // 走这一步,因为isBatchingUpdates:true
}
在requestWork中走第一步直接被return
所以案列一的执行过程:
- 第一步: this.setState({age:1})
_pendingStateQueue = [{age: 1}]
因为isBatchingUpdates为true
enqueUpdate()不走更新,走dirtyComponents.push(component)
因为isRendering为true,
requestWork // 走第一步,return,不走performSyncWork()去更新组件
- 第二步:console.log(this.stage.age);
因为第一步并没有实际去更新组件,所以此时的state为发生改变
- 第三步:this.setState()
- 第四步:this.setState({age:1})
_pendingStateQueue = [{age: 1},{name:'test'}, {age:1}]
接下来步骤,同第一步一样
然后生命周期执行完,这个时候产生了待更新的_pendingStateQueue,和待更新的dirty组件
- 第五步:什么时候去更新组件了?
开始说了,react组件渲染DOM的过程就是一个大的事务(Transaction)中
ReactUpdates.batchedUpdates(
batchedMountComponentIntoNode, // 负责初始渲染组件
componentInstance,
container,
shouldReuseMarkup,
context,
);
batchedUpdates(){
var alreadyBatchingUpdates = ReactDefaultBatchingStrategy.isBatchingUpdates;
ReactDefaultBatchingStrategy.isBatchingUpdates = true;
if (alreadyBatchingUpdates) {
return callback(a, b, c, d, e);
} else {
// 走这一步
return transaction.perform(batchedMountComponentIntoNode, null, a, b, c, d, e);
}
}
等batchedMountComponentIntoNode(组件初始化渲染完以后,生命周期走完)执行完以后
执行事务close方法中的flushBatchedUpdates,真正去更新组件
### 说说事务transaction
- transaction
wrappers (injected at creation time)
* + +
* | |
* +-----------------|--------|--------------+
* | v | |
* | +---------------+ | |
* | +--| wrapper1 |---|----+ |
* | | +---------------+ v | |
* | | +-------------+ | |
* | | +----| wrapper2 |--------+ |
* | | | +-------------+ | | |
* | | | | | |
* | v v v v | wrapper
* | +---+ +---+ +---------+ +---+ +---+ | invariants
* perform(anyMethod) | | | | | | | | | | | | maintained
* +----------------->|-|---|-|---|-->|anyMethod|---|---|-|---|-|-------->
* | | | | | | | | | | | |
* | | | | | | | | | | | |
* | | | | | | | | | | | |
* | +---+ +---+ +---------+ +---+ +---+ |
* | initialize close |
* +-----------------------------------------+
事务执行顺序: initialize --> anyMethod --> close (wrapper过程)
这里案例中anyMethod指batchedMountComponentIntoNode
react默认的批量更新策略,transaction包含了两个wrapper
1 FLUSH_BATCHED_UPDATES ,负责实际更新组件
2 RESET_BATCHED_UPDATES , 重置isBatchingUpdates = false
FLUSH_BATCHED_UPDATES
- FLUSH_BATCHED_UPDATES,
var FLUSH_BATCHED_UPDATES = {
initialize: emptyFunction,
close: ReactUpdates.flushBatchedUpdates.bind(ReactUpdates)
};
- flushBatchedUpdates
var flushBatchedUpdates = function () {
// close前执行完runBatchedUpdates方法,这是关键
transaction.perform(runBatchedUpdates, null, transaction);
...
}
};
- runBatchedUpdates
又经过一系列函数调用,合并state然后更新组件
RESET_BATCHED_UPDATES
var RESET_BATCHED_UPDATES = {
initialize: emptyFunction,
close: function () {
ReactDefaultBatchingStrategy.isBatchingUpdates = false;
}
};
合成事件调用
- 案例二
state = {
age: 0,
name: null
}
onClic = () => {
this.setState({age:1})
console.log(this.stage.age); // 0
this.setState({name:'test'})
this.setState({age:1})
console.log(this.stage.name); // null
}
合成事件关键点:
1、 合成事件中,isBatchingUpdates 被设置为 true;
2、分为try,finally两部分,finally中去实际更新
- 案列二实际执行流程
var previousIsBatchingUpdates = isBatchingUpdates;
isBatchingUpdates = true;
try {
this.setState({age:1})
console.log(this.stage.age); // 0
this.setState({name:'test'})
this.setState({age:1})
console.log(this.stage.name); // null
} finally {
isBatchingUpdates = previousIsBatchingUpdates;
if (!isBatchingUpdates && !isRendering) { // isRendering为false在合成事件中
performSyncWork(); //去更新组件
}
}
也为isBatchingUpdates为true,isRendering为false所以try代码块中的所有setState的
enqueUpdate() requestWork()都不会更新组件,只会将其放入dirty组件中
原生事件
1 没有合成事件将isBatchingUpdates是默认值false,isRendering不存在
- enqueueUpdate中
function enqueueUpdate(component) {
// 走这里直接更新
if (!batchingStrategy.isBatchingUpdates) {
batchingStrategy.batchedUpdates(enqueueUpdate, component);
return;
}
dirtyComponents.push(component);
}
- batchedUpdates
function (callback, a, b, c, d, e) {
var alreadyBatchingUpdates = ReactDefaultBatchingStrategy.isBatchingUpdates;
ReactDefaultBatchingStrategy.isBatchingUpdates = true;
if (alreadyBatchingUpdates) {
return callback(a, b, c, d, e);
} else {
// 走这里直接更新,callback就是enqueUpdate
return transaction.perform(callback, null, a, b, c, d, e);
}
}
};
- requestWork这里怎么走?
isBatchingUpdates在batchedUpdates中已经被设置为true了
requestWork这里应该走第二步直接被return
setTimeout
无论他在合成事件还是,原生事件,还是生命周期,由于他是宏任务,等到他执行的时候
并没有isRendering和isBatchingUpdates,直接走requestWork的第三个
参考文章
https://zhuanlan.zhihu.com/p/35801438
https://yeungkc.com/2019/06/30/web-react-set-state/
http://zxc0328.github.io/2017/09/28/react-16-source/ Fiber版本
https://yeungkc.com/2019/06/30/web-react-set-state/