社区编辑申请
注册/登录
Vue.js设计与实现之十三-渲染器的核心功能:挂载与更新02
开发 前端
在Vue.js的事件处理先要解决的问题,就是如何在虚拟节点中描述事件,事件是一种特殊的属性,在vnode.props对象中以字符串on开头的属性都被视作事件。

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、写在最后

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

责任编辑:姜华 来源: 前端一码平川
相关推荐

2022-06-20 22:37:25

Linux操作系统命令

2022-06-15 11:51:14

Vue3开发避坑

2022-05-17 08:39:05

VueViteTypeScript

2022-06-15 09:01:41

2022-04-04 16:53:56

2022-06-23 09:22:57

Vue技巧前端

2022-06-07 14:15:44

Vue开发工具

2022-06-15 16:16:21

分布式数据库鸿蒙

2022-04-19 23:01:54

Vue.jsDOM节点DOM树

2022-05-19 14:57:30

CSS代码工具

2022-04-01 10:41:09

Vue.js开发工具

2022-04-06 11:27:05

harmonyeTS 开发NAPI开发

2022-06-20 15:19:51

前端监控方案

2022-06-16 07:32:38

VSCodePython插件

2022-05-09 11:19:12

CSS函数开源

2022-04-18 10:37:01

鸿蒙操作系统开发工具

2022-06-16 15:54:32

前端

2022-06-13 06:33:04

浏览器浏览器插件

2022-05-31 10:30:16

元宇宙数字人高清渲染

2022-06-22 10:23:42

互联网用户IE浏览器退休

同话题下的热门内容

手把手教你用装饰器扩展 Python 计时器IOC-Golang 的 AOP 原理与应用Vue 里,多级菜单要如何设计才显得专业?分析了 700 万份工作需求,市场需求最高的八种编程语言是这些Vue 2.7 正式发布,代号为 Naruto手把手教你实现一个 Python 计时器2022 年编程语言趋势:Swift、Kotlin 热度持续增长,收入最高的五种语言竟是它们分布式事务(Seata) 四大模式详解

编辑推荐

太厉害了,终于有人能把TCP/IP协议讲的明明白白了!牛人5次面试腾讯不成功的经验HBase原理–所有Region切分的细节都在这里了Javascript如何监听页面刷新和关闭事件如何搭建一个HTTPS服务端
我收藏的内容
点赞
收藏

51CTO技术栈公众号