vue事件处理机制

绑定methods上下文

function initMethods (vm: Component, methods: Object) {

  for (const key in methods) {
   // 将method的this绑定为当前构建对象
    vm[key] = typeof methods[key] !== 'function' ? noop : bind(methods[key], vm)
  }
}

事件订阅

原生事件订阅

示例代码

  it('should bind event to a method', () => {
    vm = new Vue({
      el,
      template: '<div v-on:click="foo"></div>',
      methods: { foo: spy }
    })
    triggerEvent(vm.$el, 'click')
    expect(spy.calls.count()).toBe(1)

    const args = spy.calls.allArgs()
    const event = args[0] && args[0][0] || {}
    expect(event.type).toBe('click')
  })

编译优化后的render

(function anonymous(
) {
with(this){return _c('div',{on:{"click":foo}})}
})

patch过程中会回调create|update hook, 在hook中完成对事件的处理
src/platforms/web/runtime/modules/events.js


function updateDOMListeners (oldVnode: VNodeWithData, vnode: VNodeWithData) {
  if (isUndef(oldVnode.data.on) && isUndef(vnode.data.on)) {
    return
  }
  const on = vnode.data.on || {}
  const oldOn = oldVnode.data.on || {}
  target = vnode.elm
  normalizeEvents(on)
  updateListeners(on, oldOn, add, remove, createOnceHandler, vnode.context)
  target = undefined
}

export default {
  create: updateDOMListeners,
  update: updateDOMListeners
}
自定义事件处理

示例代码

  it('should bind to a child component', () => {
    vm = new Vue({
      el,
      template: '<bar @custom="foo"></bar>',
      methods: { foo: spy },
      components: {
        bar: {
          template: '<span>Hello</span>'
        }
      }
    })
    vm.$children[0].$emit('custom', 'foo', 'bar')
    expect(spy).toHaveBeenCalledWith('foo', 'bar')
  })

编译优化后的render

(function anonymous(
) {
with(this){return _c('bar',{on:{"custom":foo}})}
})

事件相关订阅属于子组件bar的属性,但回调函数属于父组件

  // extract listeners, since these needs to be treated as
  // child component listeners instead of DOM listeners
  const listeners = data.on

//创建bar vnode
  const vnode = new VNode(
    `vue-component-${Ctor.cid}${name ? `-${name}` : ''}`,
    data, undefined, undefined, undefined, context,
    // componentOptions
    { Ctor, propsData, listeners, tag, children },
    asyncFactory
  )

// 初始化子组件
export function initInternalComponent (vm: Component, options: InternalComponentOptions) {
  const opts = vm.$options = Object.create(vm.constructor.options)
  // doing this because it's faster than dynamic enumeration.
  const parentVnode = options._parentVnode
  opts.parent = options.parent
  opts._parentVnode = parentVnode

  const vnodeComponentOptions = parentVnode.componentOptions
  opts.propsData = vnodeComponentOptions.propsData
  // 获取父组件的订阅对象,即data.on
  opts._parentListeners = vnodeComponentOptions.listeners
  opts._renderChildren = vnodeComponentOptions.children
  opts._componentTag = vnodeComponentOptions.tag

  if (options.render) {
    opts.render = options.render
    opts.staticRenderFns = options.staticRenderFns
  }
}

// 初始化事件订阅
export function initEvents (vm: Component) {
  vm._events = Object.create(null)
  vm._hasHookEvent = false
  // init parent attached events
  const listeners = vm.$options._parentListeners
  if (listeners) {
   // 完成事件的订阅
    updateComponentListeners(vm, listeners)
  }
}


带native的处理过程

示例

  it('should be able to bind native events for a child component', () => {
    vm = new Vue({
      el,
      template: '<bar @click.native="foo"></bar>',
      methods: { foo: spy },
      components: {
        bar: {
          template: '<span>Hello</span>'
        }
      }
    })
    vm.$children[0].$emit('click')
    expect(spy).not.toHaveBeenCalled()
    triggerEvent(vm.$children[0].$el, 'click')
    expect(spy).toHaveBeenCalled()
  })

编译后代码

(function anonymous(
) {
with(this){return _c('bar',{nativeOn:{"click":function($event){return foo($event)}}})}
})

子组件编译后代码

(function anonymous(
) {
with(this){return _c('span',[_v("Hello")])}
})

//src/core/vdom/patch.js

  function initComponent (vnode, insertedVnodeQueue) {
    if (isDef(vnode.data.pendingInsert)) {
      insertedVnodeQueue.push.apply(insertedVnodeQueue, vnode.data.pendingInsert)
      vnode.data.pendingInsert = null
    }
    // placeholder 获取组件渲染结果
    vnode.elm = vnode.componentInstance.$el
    if (isPatchable(vnode)) {
      invokeCreateHooks(vnode, insertedVnodeQueue)
      setScope(vnode)
    } else {
      // empty component root.
      // skip all element-related modules except for ref (#3455)
      registerRef(vnode)
      // make sure to invoke the insert hook
      insertedVnodeQueue.push(vnode)
    }
  }

function updateDOMListeners (oldVnode: VNodeWithData, vnode: VNodeWithData) {
  if (isUndef(oldVnode.data.on) && isUndef(vnode.data.on)) {
    return
  }
  const on = vnode.data.on || {}
  const oldOn = oldVnode.data.on || {}
  // 在根节点上订阅事件
  target = vnode.elm
  normalizeEvents(on)
  updateListeners(on, oldOn, add, remove, createOnceHandler, vnode.context)
  target = undefined
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值