Vue3之事件绑定的实现逻辑详解

 更新时间:2023年11月15日 09:42:47   作者:JonnyLan  
这篇文章主要介绍了Vue3之事件绑定的实现逻辑,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教

Vue的事件绑定主要是通过v-on指令来实现的,这个指令既可以实现原生事件绑定,例如onclick等。

也可以实现组件的自定义事件,从而实现组件的数据通信。

本文我们就来分析下Vue的事件处理的逻辑。

v-on作用于普通元素

用在普通元素上时,只能监听原生 DOM 事件,最多的就是onclick事件了。

我们就以onclick事件来分析原理。

案例

let click = () => {
  console.log("点击我,很快乐")
};

<!-- template -->
<div v-on:click="click">点击我吧</div>

分析实现逻辑

我们先来看下渲染函数

const _hoisted_1 = ["onClick"]

function render(_ctx, _cache) {
  with (_ctx) {
    const { openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue

    return (_openBlock(), _createElementBlock("div", { onClick: click }, "点击我吧", 8 /* PROPS */, _hoisted_1))
  }
}

我们看到 渲染函数在创建VNode的时候传了一个onClickpros;

我们先来看下patchProp函数中对onClick这个pros的处理逻辑

export const patchProp: DOMRendererOptions['patchProp'] = (
  el,
  key,
  prevValue,
  nextValue,
  isSVG = false,
  prevChildren,
  parentComponent,
  parentSuspense,
  unmountChildren
) => {
  if (isOn(key)) {
    patchEvent(el, key, prevValue, nextValue, parentComponent)
  }
}

function patchEvent(el, rawName, prevValue, nextValue, instance = null) {
  // vei = vue event invokers
  const invokers = el._vei || (el._vei = {});
  
  if (添加) {
    const invoker = (invokers[rawName] = createInvoker(nextValue, instance));
    el.addEventListener(event, handler, options)
  } else {
    el.removeEventListener(event, handler, options)
  }
}

我们可以看出来底层就是调用的addEventListener函数进行事件监听绑定,调用removeEventListener进行事件监听解绑。

其实这个实现逻辑很容易想到,没什么难度。

重点分析—事件修饰符

oncecapturepassive

这两个可以直接作为addEventListenerremoveEventListener 的第三个参数options 中的值,因为这是W3C支持的事件可选参数。

stop, prevent,capture, self等。

这类修饰符被封装在另外一个withModifiers函数中。

export const withModifiers = (fn: Function, modifiers: string[]) => {
  return (event: Event, ...args: unknown[]) => {
    for (let i = 0; i < modifiers.length; i++) {
      const guard = modifierGuards[modifiers[i]]
      if (guard && guard(event, modifiers)) return
    }
    return fn(event, ...args)
  }
}

这里设计的非常精妙,每个修饰符都对应一个执行函数,如果调用执行函数guard(event, modifiers)返回true, 则函数withModifiers就直接返回了,不会再执行事件的函数fn(event, ...args)了。

这里列一些这些修饰符对应的函数:

const modifierGuards: Record<
  string,
  (e: Event, modifiers: string[]) => void | boolean
> = {
  stop: e => e.stopPropagation(),
  prevent: e => e.preventDefault(),
  self: e => e.target !== e.currentTarget,
  ctrl: e => !(e as KeyedEvent).ctrlKey,
  shift: e => !(e as KeyedEvent).shiftKey,
  alt: e => !(e as KeyedEvent).altKey,
  meta: e => !(e as KeyedEvent).metaKey,
  left: e => 'button' in e && (e as MouseEvent).button !== 0,
  middle: e => 'button' in e && (e as MouseEvent).button !== 1,
  right: e => 'button' in e && (e as MouseEvent).button !== 2,
  exact: (e, modifiers) =>
    systemModifiers.some(m => (e as any)[`${m}Key`] && !modifiers.includes(m))
}

v-on作用于组件绑定自定义事件

实现案例

父组件中 有个子组件son, 使用v-on绑定了子组件的自定义事件,还有一个p显示当前的时间戳。

<Son v-on:children-clicked="childClickedHandler" />
<p>{{ date }}</p>

setup() {

  let childClickedHandler = (data: Date) => {
    date.value = data.getTime();
  }

  let date = ref(new Date().getTime());

  return {
    date,
    childClickedHandler
  };
},

子组件中有一个div, 每次点击会触发自定义事件childrenClicked, 并且传递了一个参数值为当前时间。

<div v-on:click="clickevent">点击我吧</div>

emits: ["childrenClicked"],
setup(props, {emit}) {

  let clickevent = () => {
    emit('childrenClicked', new Date());
  }
  return {clickevent};
},            

这样点击子组件后就会触发父组件的childClickedHandler方法,从而更新当前时间戳的显示。

接下来我们就来看看这底层的逻辑是如何实现的?

实现逻辑

先看下两个组件的渲染函数的重点部分

父组件:

_createVNode(_component_Son, { onChildrenClicked: childClickedHandler }, null, 8 /* PROPS */, ["onChildrenClicked"])

父组件给子组件绑定自定义事件是传递了一个事件pro,这个pro的名称用驼峰命名, 例如本例中的onChildrenClicked

子组件:

const _hoisted_1 = ["onClick"]

_createElementBlock("div", {
    onClick: $event => ($emit('childrenClicked', new Date()))
}, "点击我吧", 8 /* PROPS */, _hoisted_1)

子组件div点击的绑定前面说过,点击的时候执行$emit('childrenClicked', new Date(), 这个没有什么特别的。

现在的问题就是为什么子组件$emit('childrenClicked', new Date()如何找到父组件的onChildrenClicked方法并执行?

$emit来自于createSetupContext函数调用时候传入的参数setupContext

export function createSetupContext(
  instance: ComponentInternalInstance
): SetupContext {
    return {
      get attrs() {
        return attrs || (attrs = createAttrsProxy(instance))
      },
      slots: instance.slots,
      emit: instance.emit,
      expose
    }
  }
}

$emit就是组件实例的emit方法。

实例的emit方法用于寻找对应的自定义事件的函数

export function emit(
  instance: ComponentInternalInstance,
  event: string,
  ...rawArgs: any[]
) {
  const props = instance.vnode.props || EMPTY_OBJ

  // 传入的传参
  let args = rawArgs
  
  // TODO: 处理v-mode的方法
  const isModelListener = event.startsWith('update:')

  // 处理函数名,on+首字母大写的函数名 或者 on+驼峰命名的函数名 
  let handlerName
  let handler =
    props[(handlerName = toHandlerKey(event))] ||
    props[(handlerName = toHandlerKey(camelize(event)))]
  if (!handler && isModelListener) {
    handler = props[(handlerName = toHandlerKey(hyphenate(event)))]
  }

  if (handler) {
    // 调用函数,参数是外部传入的参数
    callWithAsyncErrorHandling(
      handler,
      instance,
      ErrorCodes.COMPONENT_EVENT_HANDLER,
      args
    )
  }
  
}

如果函数名以update:开头,说明是一个v-model的修改数据函数,这部分逻辑会在v-model专门的文章中介绍;

然后在实例对象的props中找 on+首字母大写的函数名 的函数,如果没找到,则找 on+首字母大写且驼峰命名的函数名 的函数;

如果找到了对应的函数,则调用函数,调用函数的参数为传入的参数。

总结

v-on作用于普通元素底层是利用 addEventListenerremoveEventListener,修饰符要么利用W3C标准,要么利用函数调用来实现;

v-on作用于组件是 子组件利用 emitpro 中搜寻到对应的函数(由父组件传入),然后执行对应的函数。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • 基于vue-ssr服务端渲染入门详解

    基于vue-ssr服务端渲染入门详解

    这篇文章主要介绍了基于vue-ssr服务端渲染入门详解,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-01-01
  • vue如何实现Toast轻提示

    vue如何实现Toast轻提示

    这篇文章主要介绍了vue如何实现Toast轻提示,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-04-04
  • 浅谈Vue单页面做SEO的四种方案

    浅谈Vue单页面做SEO的四种方案

    Vue SPA单页面应用对SEO不友好,当然也有相应的解决方案,通过查找资料,大概有以下4种方法,本文就详细的介绍一下
    2021-10-10
  • Element Tooltip 文字提示的使用示例

    Element Tooltip 文字提示的使用示例

    这篇文章主要介绍了Element Tooltip 文字提示的使用示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-07-07
  • vue+webpack模拟后台数据的示例代码

    vue+webpack模拟后台数据的示例代码

    这篇文章主要介绍了vue+webpack模拟后台数据的示例代码,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-07-07
  • 在vant中使用时间选择器和popup弹出层的操作

    在vant中使用时间选择器和popup弹出层的操作

    这篇文章主要介绍了在vant中使用时间选择器和popup弹出层的操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-11-11
  • vue新玩法VueUse工具库具体用法@vueuse/core详解

    vue新玩法VueUse工具库具体用法@vueuse/core详解

    这篇文章主要介绍了vue新玩法VueUse-工具库@vueuse/core,VueUse不是Vue.use,它是一个基于 Composition API 的实用函数集合,下面是具体的一些用法,需要的朋友可以参考下
    2022-08-08
  • vue调用本地缓存方式(监视数据变更)

    vue调用本地缓存方式(监视数据变更)

    这篇文章主要介绍了vue调用本地缓存方式(监视数据变更),具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-04-04
  • Vue3.2.x中的小技巧及注意事项总结

    Vue3.2.x中的小技巧及注意事项总结

    Vue是一套用于构建用户界面的渐进式JavaScript框架,是目前最火的前端框架之一,是前端工程师的必备技能,下面这篇文章主要给大家介绍了关于Vue3.2.x中的小技巧及注意事项的相关资料,需要的朋友可以参考下
    2022-04-04
  • 如何构建 vue-ssr 项目的方法步骤

    如何构建 vue-ssr 项目的方法步骤

    这篇文章主要介绍了如何构建 vue-ssr 项目的方法步骤,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-08-08

最新评论