Vue 2 阅读理解(十一)之 组件事件系统初始化

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: Vue 2 阅读理解(十一)之 组件事件系统初始化

initEvents 组件事件初始化


initLifecycle 初始化生命周期执行结束后,紧接着就是执行 initEvents(vm) 来初始化实例的事件绑定。


这里的事件指的是在组件或者元素上通过 v-on 或者 @ 作为属性前缀的自定义元素属性,在 Vue 中会解析为自定义事件。


1. 查询和解析事件


在之前的 compile 模板解析部分,最终会调用 parseHTML() 方法来解析模板字符串并生成 AST 对象;并且在调用该函数之前定义了 closeElement(), start(), end() 几个方法,用来处理解析到不同数据时的处理方法。


其中,如果是自闭合标签,会在 start() 方法中直接调用 closeElement() 结束这个标签的解析,否则则在解析到结尾标签的时候通过 end() 方法来调用 closeElement() 结束处理。而在 closeElement 方法里面,就会通过 processElement 来处理 Vue 关键字属性和其他属性。这里事件定义部分也会当做普通属性,解析到 ast 对象的 attrsattrsList 中,但是一个新属性(events)用来标注事件。


大致格式如下:


{
  attrs: [],
  attrsList: [
    {
      "name": "v-on:click.stop",
      "value": "demoTwoClick",
      "start": 599,
      "end": 624
    },
    {
      "name": "@change-watch",
      "value": "demoTwoChange",
      "start": 625,
      "end": 654
    }
  ],
  attrsMap: {
    "ref": "demoTwo",
    "key": "demo2",
    "v-on:click.stop": "demoTwoClick",
    "@change-watch": "demoTwoChange"
  },
  events: {
    "click": {
      "value": "demoTwoClick",
      "dynamic": false,
      "modifiers": { stop: true },
      "start": 599,
      "end": 624
    },
    "change-watch": {
      "value": "demoTwoChange",
      "dynamic": false,
      "start": 625,
      "end": 654
    }
  }
}


attrs 数组会对绑定属性进行处理,剔除掉事件定义部分;attrsList 则包含完整的属性配置;attrsMap 包含属性的属性配置名和属性值的对应关系。


整个事件的解析过程大致为: processElement() => processAttrs() => addHandler() 。最终会生成一个字符串拼接到 render 函数的执行中,用来创建 VNode 与真实 DOM


在某个组件的模板内部的 html 原始元素上定义的事件,都 “不会” 被解析到组件的事件系统中。但是会在对应元素节点的 AST 对象上增加一个事件绑定,并将事件函数上下文指定为当前实例


2. initEvents 事件对象初始化


这部分都用来解析组件上的事件绑定,所以内部的函数命名都与 component 相关,源码位于 src/core/instance/events.ts


export function initEvents(vm: Component) {
  vm._events = Object.create(null)
  vm._hasHookEvent = false
  const listeners = vm.$options._parentListeners
  if (listeners) {
    updateComponentListeners(vm, listeners)
  }
}
export function updateComponentListeners(vm: Component, listeners: Object, oldListeners?: Object | null) {
  target = vm
  updateListeners(
    listeners,
    oldListeners || {},
    add,
    remove,
    createOnceHandler,
    vm
  )
  target = undefined
}


这里首先会在实例上创建一个空对象属性 _events ,用来保存组件事件;并创建一个生命周期监听事件的标识属性 _hasHookEvent


然后读取父组件的注册事件,如果存在事件,则调用 updateComponentListeners 将事件注册到子组件实例中。


updateComponentListeners 函数内部的定义也十分简单,就是 指定 this 指向 并调用 updateListeners 注册事件。


export function updateComponentListeners(vm: Component, listeners: Object, oldListeners?: Object | null) {
  target = vm
  updateListeners(
    listeners,
    oldListeners || {},
    add,
    remove,
    createOnceHandler,
    vm
  )
  target = undefined
}


3. updateListeners 事件注册


该函数位于 src/core/vdom/helpers/update-listeners.ts 内,主要是根据上面解析出来的事件定义来注册对应的事件,其函数定义如下:


export function updateListeners(
  on: Object,
  oldOn: Object,
  add: Function,
  remove: Function,
  createOnceHandler: Function,
  vm: Component
) {
  let name, cur, old, event
  for (name in on) {
    cur = on[name]
    old = oldOn[name]
    event = normalizeEvent(name)
    if (isUndef(cur)) {
      __DEV__ && warn('')
    } else if (isUndef(old)) {
      if (isUndef(cur.fns)) {
        cur = on[name] = createFnInvoker(cur, vm)
      }
      if (isTrue(event.once)) {
        cur = on[name] = createOnceHandler(event.name, cur, event.capture)
      }
      add(event.name, cur, event.capture, event.passive, event.params)
    } else if (cur !== old) {
      old.fns = cur
      on[name] = old
    }
  }
  for (name in oldOn) {
    if (isUndef(on[name])) {
      event = normalizeEvent(name)
      remove(event.name, oldOn[name], event.capture)
    }
  }
}


这里首先是遍历 新事件定义对象,并与之前的事件对象进行对象(如果当前事件中没有定义该事件函数,则报错)。


如果原事件 没有定义,则调用 createFnInvoker() 进行定义;但如果是一个 .once 修饰的事件,则会用 createOnceHandler() 重新定义该事件;最后将其添加到当前实例的 $on 属性中。


如果原事件中 有事件定义,但与新定义事件对象地址不同,则会将原事件定义的 函数定义部分 重新指向为当前新的事件定义,并赋值给新事件对象中即可。


最后,则是遍历原事件对象,将不存在与新事件对象中的事件去除(以事件名作为区分)


当然,在遍历之前还调用了 normalizeEvent 函数来格式化事件对象。


4. normalizeEvent 事件对象格式化


该函数定义十分简单,就是一个带缓存作用的字符串截断方法。


const normalizeEvent = cached(
  (name: string): {
    name: string
    once: boolean
    capture: boolean
    passive: boolean
    handler?: Function
    params?: Array<any>
  } => {
    const passive = name.charAt(0) === '&'
    name = passive ? name.slice(1) : name
    const once = name.charAt(0) === '~'
    name = once ? name.slice(1) : name
    const capture = name.charAt(0) === '!'
    name = capture ? name.slice(1) : name
    return { name, once, capture, passive }
  }
)


这里其实就是根据 @ 或者 v-on: 后的字符串,根据不同的定义来进行截断,返回一个包含 真实事件名、修饰符声明的对象。


5. createFnInvoker 创建事件调用


这个函数其实就是使用闭包方式返回一个调用函数 invoker,并添加 invoker.fns 属性,以 数组形式 保存这个事件名对应的所有事件。最后在调用 invoker() 时,内部会遍历 invoker.fns ,利用函数的 apply 方法改变指向为注册时的 Vue 实例并触发事件。


相关文章
|
5月前
|
在 Vue 中处理组件选项与 Mixin 选项冲突的详细解决方案
【10月更文挑战第18天】通过以上的分析和探讨,相信你对在 Vue 中使用 Mixin 时遇到组件选项与 Mixin 选项冲突的解决方法有了更深入的理解。在实际开发中,要根据具体情况灵活选择合适的解决方案,以确保代码的质量和可维护性。
182 7
Vue3中v-model在处理自定义组件双向数据绑定时有哪些注意事项?
在使用`v-model`处理自定义组件双向数据绑定时,要仔细考虑各种因素,确保数据的准确传递和更新,同时提供良好的用户体验和代码可维护性。通过合理的设计和注意事项的遵循,能够更好地发挥`v-model`的优势,实现高效的双向数据绑定效果。
192 64
Vue3中v-model在处理自定义组件双向数据绑定时,如何避免循环引用?
Web 组件化是一种有效的开发方法,可以提高项目的质量、效率和可维护性。在实际项目中,要结合项目的具体情况,合理应用 Web 组件化的理念和技术,实现项目的成功实施和交付。通过不断地探索和实践,将 Web 组件化的优势充分发挥出来,为前端开发领域的发展做出贡献。
82 8
在 Vue 3 中,如何使用 v-model 来处理自定义组件的双向数据绑定?
需要注意的是,在实际开发中,根据具体的业务需求和组件设计,可能需要对上述步骤进行适当的调整和优化,以确保双向数据绑定的正确性和稳定性。同时,深入理解 Vue 3 的响应式机制和组件通信原理,将有助于更好地运用 `v-model` 实现自定义组件的双向数据绑定。
Vue 组件间通信的最佳实践
本文总结了 Vue.js 中组件间通信的多种方法,包括 props、事件、Vuex 状态管理等,帮助开发者选择最适合项目需求的通信方式,提高开发效率和代码可维护性。
Vue 组件间如何通信
Vue组件间通信是指在Vue应用中,不同组件之间传递数据和事件的方法。常用的方式有:props、自定义事件、$emit、$attrs、$refs、provide/inject、Vuex等。掌握这些方法可以实现父子组件、兄弟组件及跨级组件间的高效通信。
Vue 的动态组件与 keep-alive
【10月更文挑战第19天】总的来说,动态组件和 `keep-alive` 是 Vue.js 中非常实用的特性,它们为我们提供了更灵活和高效的组件管理方式,使我们能够更好地构建复杂的应用界面。深入理解和掌握它们,以便在实际开发中能够充分发挥它们的优势,提升我们的开发效率和应用性能。
84 18
Vue 中实现组件的懒加载
【10月更文挑战第23天】组件的懒加载是 Vue 应用中提高性能的重要手段之一。通过合理运用动态导入、路由配置等方式,可以实现组件的按需加载,减少资源浪费,提高应用的响应速度和用户体验。在实际应用中,需要根据具体情况选择合适的懒加载方式,并结合性能优化的其他措施,以打造更高效、更优质的 Vue 应用。
vue3知识点:Suspense组件
vue3知识点:Suspense组件
58 4
《vue3第五章》新的组件,包含:Fragment、Teleport、Suspense
《vue3第五章》新的组件,包含:Fragment、Teleport、Suspense
65 2

热门文章

最新文章

AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等