function dispatchEvent(event){
+ updateTracker.isBatchingUpdate = true;
let { target, type } = event;
let eventType = `on${type}`;
let syntheticEvent = createSyntheticEvent(event);
// 模拟事件冒泡,一直向父级元素进行冒泡
while(target){
let { store } = target;
let handler = store && store[eventType];
handler && handler.call(target, syntheticEvent);
target=target.parentNode;
}
+ updateTracker.batchUpdate();
}
在上一步简单地使用到了
setState
这个API,但使用起来用点粗暴,一调用setState
就更新组件了.而React一开始就告诉了我们,setState
它的执行不保证同步还是异步的.因为它会尽可能将多次setState
合并到一次来执行,以提高渲染的性能.按照官方的说法: 在事件处理函数内部的
setState
是异步的.怎么理解呢,也就是在我们绑定的事件里面,在该事件处理可控制的范围内.它是异步的,那什么是可控制范围之外了呢?比如遇到了
setTimeout
,Promise
等,它就会变成同步了.来一个比较坑的组件,想想它会在控制台输出什么?
对于这个组件的输出,不管你的信心怎样,咱们先放一边,回来思考一下要实现
setState
的异步更新,并且是批量更新.既然要批量,那我们就需要维护一个队列,同时需要有一个标识,标识如果是处理异步批量更新状态的话,那就把setState
放入这个队列中.当达到需要更新的时机时,就把队列里面的数据进行合并一次性完成更新操作.那么我们在调用
setState
这个API的时候,就不能直接去修改父类里面的那个state
了.为了保持父类API的纯净,我们抽离一个Updater
类来维护这个队列.就把它叫作更新器吧.新建
src/react/Updater.js
文件,通过pendingState
来保存要更新的队列,componentInstance
挂载的是组件的实例,后面方便通过它来调用组件上的一些方法.修改
src/react/Component.js
文件接下来,那如何来维护这个标识呢?事件!对,React告诉了我们,在事件处理函数中,前面我们正好也实现了事件的处理,那就可以在事件执行的开始,把这个标识设置为
true
,事件处理完了,再进行批量更新,并把这个标识恢复成false
即可.在
src/react/Updater.js
中新增一个updateTracker
对象,用来维护这个标识,另外也要维护一个Updater
的实例队列,因为在一次事件过程当中,可能会触发导致多个组件的更新,而每个组件是对应一个Updater
实例的.也就是说,Updater
实例维护对应组件的更新队列,而updateTracker
需要维护多个组件的更新器(即Updater
).同时对外暴露一个batchUpdate
的方法,来批量调用每个组件的更新方法.修改
src/react-dom/event.js
文件,导入updateTracker
并修改
dispatchEvent
方法,在执行之前设置标识为true
,执行结束之后,调用批量更新来进行更新操作.接下来在
Updater
里面的addState
就可以通过这个标识符来做判断了,如果是批量更新,则把当前Updater
实例放入updateTracker
中,否则就直接同步执行更新操作.Updater
实例只需要放入一次,所以在Updater
里面也增加一个标识来判断一下.修改
src/react/Updater.js
文件继续在
Updater
里面实现上一步需要的updateComponent
方法,通过合成最新的state
,赋值给组件,并调用组件上面的foreUpdate
即可.所以同时写一个getState
方法,执行Updater
队列pendingState
里面的数据.这里面的队列有可能是一个新值对象,也有可能是一个函数,就像开篇里面的一个写法.所以在
Updater
这个类里面增加两个方法如下:并在
updateTracker
的batchUpdate
方法中调用这个updateComponent
方法在
src/index.js
文件中使用开篇我们写的那个组件,尝试一下是否已经实现了异步批量更新呢?以及尝试一下导入官方的react
和react-dom
来验证一下输出结果是否一致.通过输出我们也可以得知,在遇到
setTimeout
这些调用浏览器的API时,事件执行会继续执行后面的逻辑,直到结束或者遇到await
异步等待(执行完同步方法,参考开篇里面的await
里面去掉setTimeout
),这个时候批量更新操作完成,后续的setTimeout
方法里面的,Promise
异步方法里面,以及await
后面的setState
都会变成同步执行了.