文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

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

2024-12-02 03:31

关注

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

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

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

来源:前端一码平川内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯