React合成事件和批量更新详细实现原理(干货满满)

517 阅读4分钟

前言

  • state的更新会被合并,当你调用setState()的时候,React会把你提供的对象合并到当前的state
  • state的更新可能是异步的
    • 出于性能考虑,React可能会把多个setState()调用合并成一个调用
    • 因为this.props和this.state可能会异步更新,所以你不要依赖他们的值来更新下一个状态
    • 可以让 setState()接收一个函数而不是一个对象。这个函数用上一个state作为第一个参数
  • 事件处理
    • React事件的命名采用小驼峰式(cameCase)而不是纯小写
    • 使用JSX语法时你需要传入一个函数作为事件处理函数,而不是一个字符串
    • 你不能返回false的方式阻止默认行为,你必须显示的使用preventDefault
  • 在事件函数里面更新是异步的、批量的,在setTimeout里面是同步的,不批量的
  • 由于react设计导致了setTimeout里是同步的,因为在react中控制变量更新,是用一个标记变量isBatchingUpdate,在事件函数执行前开启,然后执行事件函数,然后关闭批量更新,setTimeout里的代码在执行的时候,批量已经关闭了,所以就是setTimeout里就是同步的
  • 值得注意的是,在React18之后,在setTimeout里也是批量了,在最新版里用的是更新优先级来合并的

下面我们开始实现

让我们回到Component.js文件中(前面篇章有说到)

image.png

  • 创建一个更新队列updateQuene

在更新器updater类中,emitUpdate触发更新函数中增加逻辑

image.png

  • 如果批量更新标识开启,则把当前的updater添加到set里保存,否则就直接更新

到这里可能会有人有疑惑,批量更新跟事件有什么关系?我们往下看

我们在之前的文章中先简单的绑定了事件

image.png

  • 我们之前的绑定事件直接就给它赋值了,无法做一些自定义的逻辑
  • 现在我们要对它进行代理,合成事件

我们先在src下创建一个event.js文件

在event.js中先写一个addEvent方法

image.png

  • 参数分别是真实的DOM节点、事件类型和原始的事件处理函数
  • 先取dom身上的store属性,这个store是自定义的属性对象,叫什么都可以
  • 把处理函数保存到真实DOM节点上
  • 判断如果document上没有这个事件,就去绑定一个自定义的事件dispatchEvent,当你点击组件中你自己写的onxxx的时候就会冒泡到这里

dispatchEvent方法

image.png

image.png

  • 先引入我们刚才在component里写的那个更新队列
  • 第一步先把批量更新标志设置为true
  • 然后拿到事件源和类型,target是真实DOM,type是事件类型,click
  • 拼成on开头,然后去真实DOM上找store自定义属性
  • 然后取出对应的事件,如果有值就执行事件,传入事件源

此处总结

  • 在一个组件初始渲染的时候,如果命中属性是以on加一个大写字母开头就会在该属性所在的真实DOM身上绑定一个自定义属性store来存放这个真实DOM身上所绑定的所有不同事件类型的事件。
  • 然后判断document上是否绑定过该事件类型的事件,如果没有绑定过,就在document上绑定一个自定义事件,在这个自定义事件中,我们会先去开启批量更新,然后拿到你点击的事件源和事件类型,先去把事件类型拼成以on开头,然后取出所点击的真实DOM身上的store,然后用刚才拼成的以on开头的事件类型去这个store里找,如果能找到,就执行该事件,并传入事件源,执行完整个事件之后,调用更新队列中的批量更新方法,在这个批量更新方法里会先关闭批量更新标识,然后进行批量更新

合成事件对象

image.png

  • 消除浏览器差异

image.png

  • 做一些兼容处理

兼容方法

image.png

我们上述逻辑还存在问题,如果子级和父级同时帮事件,点击子级是不会冒泡的

image.png

  • 原因就是你点了button,会直接 到document,div上的点击事件并不会触发

模拟事件冒泡

image.png

  • 循环事件源,不断向上找父节点

模拟完事件冒泡后,我们还需要去实现可阻止逻辑

image.png

  • 如果当前合成事件上的isPropagationStopped上的标识为true,那么我们就跳出循环

然后回到react-dom.js中修改逻辑

image.png

image.png

至此我们就实现了react合成事件和批量更新