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

1、写在前面

在上篇文章中介绍了虚拟节点的挂载与更新,以及虚拟DOM节点上的属性设置,封装了新的卸载函数unmount。那么,虚拟节点上的事件又是如何处理的呢,同一个事件设置多个处理函数,同一个元素绑定多个事件,触发事件和绑定事件的时机问题应该如何处理?

2、事件的处理

在Vue.js的事件处理先要解决的问题,就是如何在虚拟节点中描述事件,事件是一种特殊的属性,在vnode.props对象中以字符串on开头的属性都被视作事件。

const vnode = {
type:"p",
props:{
// 同一个事件多个事件处理函数
onClick:[
()=>{
//...
},
()=>{
//...
}
],
// 同一个元素绑定多个事件
onContextMenu(){
//...
}
},
children:"text"
}
renderer.render(vnode, document.querySelector("#app"));

在上面代码中,我们看到同一的DOM元素上可以绑定多个事件,同一个事件上又可以有多个事件处理函数。多次我们修改patchProps函数中事件处理相关代码得到:

patchProps(el, key, prevValue, nextValue){
if(/^on/.test(key)){
const invokers = el._vei || (el._vei = {});
let invoker = invokers[key];
const name = 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(name, invoker);
}else{
invoker.value = nextValue;
}
}else if(key === "class"){
//...
}else if(shouleSetAsProps(el, key, nextValue)){
//...
}else{
//...
}
}
}

在上面代码中,先通过/^on/.test(key)检测元素上以on开头的属性,在绑定事件时伪造事件处理函数invoker

  • 如果invoker不存在时,将invoker作为事件处理函数,缓存到el._vei属性中
  • 将真正的事件处理函数设置为invoker.value属性的值,伪造的事件处理函数invoker绑定到元素上

将el._vei的数据结构设计为一个对象,键即为事件名称,值为对应的事件处理函数,这样就不会出现事件覆盖的现象。当上面invoker.value的类型是数组时,数组中的每个元素都是一个独立的事件处理函数,且这些事件处理函数都能够正确绑定到对应元素上。

3、事件冒泡与更新时机问题

在事件处理中,需要注意处理事件冒泡和更新时机结合导致的问题,事件触发的时间会早于事件处理函数被绑定的时间。

const {effect, ref} = VueReactivity;
const bol = ref(false);
effect(()=>{
//创建vnode
const vnode = {
type:"div",
props:bol.value ? {
onClick(){
//...
}
}:{},
children:[{
type:"p",
props:{
onClick(){
bol.value = true;
}
},
children:"pingping"
}]
}
//渲染vnode
renderer.render(vnode, document.querySelector("#app"));
})

在上面代码中进行理论分析,首次渲染后由于bol.value的初始值为false,对此渲染器并不会给div元素绑定点击事件。在鼠标点击p元素后,bol.value的值变更为true,看到点击事件会从子元素p冒泡到父元素div上,但是div元素又没有绑定事件,因此啥也不发生。

但是,事实上在点击p元素时,父元素div的click事件触发了执行函数的执行。这是因为bol是个响应式数据,在点击p元素后,bol.value的值发生改变,会触发副作用函数的重新执行。而在更新阶段,渲染器会给div元素绑定click事件,在更新完后点击事件才从p元素冒泡到div元素。

触发事件的时机与事件绑定的时机的联系

在一个事件触发时,目标元素上还没有绑定相关的事件处理函数,因此屏蔽所有绑定事件时机要晚于触发时间的事件处理函数的执行。

patchProps(el, key, prevValue, nextValue){
if(/^on/.test(key)){
const invokers = el._vei || (el._vei = {});
let invoker = invokers[key];
const name = key.slice(2).toLowerCase();
if(nextValue){
if(!invoker){
invoker = el._vei[key] => {
//e.timeStamp是事件发生的时间,如果事件触发的时机早于事件绑定的时间,则不执行事件处理函数
if(e.timeStamp < invoker.attached) return;
//invoker.value是数组时,遍历逐个调用事件处理函数
if(Array.isArray(invoker.value)){
invoker.value.forEach(fn=>fn(e));
}else{
invoker.value(e);
}
}
invoker.value = nextValue;
// 添加invoker.attached属性,存储事件处理函数被绑定的时间
invoker.attached = performance.now();
el.addEventListener(name, invoker);
}else{
invoker.value = nextValue;
}
}else if(key === "class"){
//...
}else if(shouleSetAsProps(el, key, nextValue)){
//...
}else{
//...
}
}
}

在上面代码中,给伪造的事件处理函数添加了invoker.attached属性,用于存储事件处理函数被绑定的时间。在invoker执行的时候,通过事件对象e.timeStamp获取事件发生的时间,比较两者的时间,如果事件触发的时机早于事件绑定的时间,则不执行事件处理函数。

4、写在最后

在本文中主要讨论了事件的处理,介绍了在虚拟节点上绑定事件,如何绑定和更新事件。同时,还介绍了如何处理触发事件与更新时机的问题,屏蔽所有绑定事件时机要晚于触发时间的事件处理函数的执行。

名称栏目:Vue.js设计与实现之十三-渲染器的核心功能:挂载与更新02
分享链接:http://www.shufengxianlan.com/qtweb/news0/478450.html

网站建设、网络推广公司-创新互联,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等

广告

声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 创新互联