当前位置: 首页 > 科技观察

Vue.js设计与实现十三——渲染器的核心功能:挂载和更新02

时间:2023-03-16 22:52:53 科技观察

1,上一篇写的,介绍了虚拟节点的挂载和更新,以及虚拟DOM节点的属性设置,其中封装新的卸载函数unmount。那么,如何处理虚拟节点上的事件呢?为同一个事件设置多个处理函数,用同一个元素绑定多个事件,触发事件和绑定事件的时机如何处理?2.事件处理Vue.js的事件处理首先要解决的问题是如何在虚拟节点中描述事件。event是一个特殊的属性,将vnode.props对象中以on开头的属性视为事件。constvnode={type:"p",props:{//同一事件的多个事件处理程序onClick:[()=>{//...},()=>{//...}],//将多个事件绑定到同一个元素onContextMenu(){//...}},children:"text"}renderer.render(vnode,document.querySelector("#app"));在上面的代码中,我们看到多个事件可以绑定到同一个DOM元素,并且同一个事件可以有多个事件处理程序。我们多次修改patchProps函数中的事件处理相关代码得到:patchProps(el,key,prevValue,nextValue){if(/^on/.test(key)){constinvokers=el._vei||(el._vei={});让invoker=invokers[key];constname=key.slice(2).toLowerCase();if(nextValue){if(!invoker){invoker=el._vei[key]=>{//当invoker.value为数组时,逐一遍历调用事件处理函数if(Array.isArray(invoker.value)){invoker.value.forEach(fn=>fn(e));}else{invoker.value(e);}}invoker.value=nextValue;el.addEventListener(名字,调用者);}else{invoker.value=nextValue;}}elseif(key==="class"){//...}elseif(shouleSetAsProps(el,key,nextValue)){//...}else{//...}}}在上面的代码,首先通过/^on/.test(key)检查元素上以on开头的属性,在绑定事件时伪造事件处理器调用者。如果调用者不存在,则使用调用者作为事件处理程序并将其缓存在el._vei属性中。将真正的事件处理程序设置为invoker.value属性的值,伪造事件处理程序invoker绑定到元素,el._vei的数据结构设计为对象,key为事件名,value为是对应的事件处理函数,这样就不会出现任何事件覆盖现象当上述invoker.value的类型为数组时,数组中的每个元素都是一个独立的事件处理器,这些事件处理器可以正确绑定到对应的元素上。3、事件冒泡和更新时机的问题在事件处理中,需要注意事件冒泡和更新时机结合使用所带来的问题。事件触发的时间会早于事件处理函数绑定的时间。const{effect,ref}=VueReactivity;constbol=ref(假);effect(()=>{//createvnodeconstvnode={type:"div",props:bol.value?{onClick(){//...}}:{},children:[{type:"p",props:{onClick(){bol.value=true;}},children:"pingping"}]}//渲染vnoderenderer.render(vnode,document.querySelector("#app"));})上面代码中的理论分析,第一次渲染后,由于bol.value的初始值为false,所以渲染器不会绑定div元素的Click事件。鼠标点击p元素后??,bol.value的值变为true,点击事件会从子元素p冒泡到父元素div,但是div元素没有绑定事件,所以什么也没有发生。但实际上,当p元素被点击时,父元素div的click事件触发了执行函数的执行。这是因为bol是响应式数据。p元素被点击后,bol.value的值发生变化,会触发副作用函数的重新执行。在update阶段,renderer会将click事件绑定到div元素上,更新后click事件会从p元素冒泡到div元素。事件触发时机与事件绑定时机的关系当一个事件被触发时,目标元素还没有绑定相关的事件处理器,所以所有绑定事件时机晚于触发时间的事件处理器的执行被封锁。patchProps(el,key,prevValue,nextValue){if(/^on/.test(key)){constinvokers=el._vei||(el._vei={});让invoker=invokers[key];constname=key.slice(2).toLowerCase();if(nextValue){if(!invoker){invoker=el._vei[key]=>{//e.timeStamp是事件发生的时间,如果事件被触发如果时间早于事件绑定时间,事件处理函数不会执行if(e.timeStampfn(e));}else{invoker.value(e);}}invoker.value=nextValue;//添加invoker.attached属性,存储事件处理器为Bindingtimeinvoker.attached=performance.now();el.addEventListener(名字,调用者);}else{invoker.value=nextValue;}}elseif(key==="class"){//...}elseif(shouleSetAsProps(el,key,nextValue)){//...}else{//...}}}在上面的代码,invoker.at被添加到假事件处理程序中tached属性用于存储事件处理函数的绑定时间。执行调用者时,通过事件对象e.timeStamp获取事件发生时间,比较两个时间。如果事件触发时间早于事件绑定时间,则不执行事件处理程序。4.写在最后。本文主要讨论事件的处理,介绍如何在虚拟节点上绑定和更新事件。同时也介绍了如何处理触发事件和更新时机的问题,屏蔽了所有绑定事件时机晚于触发时间的事件处理函数的执行。