1x2 精读Vue官方文档 - 自定义事件

发布于 2022年 02月 26日 16:55

精读 Vue 官方文档系列 🎉

事件名

”事件“与”组件“或”Prop“不同,它不存在大小写转换的问题,事件的监听与触发都是基于事件名的全匹配。但我们仍然推荐使用 kebab-case 风格为事件命名。

自定义组件的 v-model

  • v-model 本质是一个语法糖,用来实现数据的双向绑定(注意此时组件之间的数据传递不在是单向下行绑定的,因为子组件也可以修改父组件中的数据(事件触发))。
  • v-model 在组件上封装了一个名为 value 的 Prop 与名为 input 的事件。

<!--v-model 完整形式-->
<base-input :value="value" @input="value=$event" />

<!--v-model 语法糖-->
<base-input v-model="value" />

但是并不是所有组件的值和事件名都是 valueinput,例如单选按钮、复选框以及自定义组件等。

特别是自定义组件的 v-model 功能需要开发者自己去实现。

一旦我们组件使用了 v-model 指令,Vue 会默认在这个组件上附加 value 属性用来保存值,并绑定一个 input 事件来接收和改变值,所以,利用这一默认的行为,我们可以快速的在自定义组件中实现 v-model 功能:

Vue.component("base-checkbox", {
    inheritAttrs:false,
    props:{
        value:Boolean //v-model 指令会自动绑定一个 value 属性来向组件内部传递值,这个值会在组件外,被对应的 input 事件更新。
    },
    //v-model 指令会自动绑定一个 input 事件来改变 value,并循环向通过 props 向组件内部传递。
    template:`<input type="checkbox" :checked="value" @change="$emit('input', $event.target.value)"` 
})

现在,通过组件的 model 配置选项,我们就可以自定义 v-model 的属性与事件名称,实现更大程度的自定义。

Vue.component("base-checkbox", {
    inheritAttrs:false,
    model:{
        prop:'checked',
        event:'change'
    },
    props:{
        checked:Boolean
    },
    template:`<input type="checkbox" :checked="checked" @change="$emit('change', $event.target.value)"`
})

虽然 v-model 默认 Prop 是 value 或者基于 model.prop 选项进行自定义的 Prop,但我们仍然需要在组件的 props 进行声明。

通过 .sync 修饰符来规范双向数据绑定

虽然 v-model 提供了强大的双向数据绑定功能,但与之带来的问题就是父子组件的数据流向变得不再清晰,因为子组件也可以修改父组件中的数据,数据传递不再遵循单向下行绑定的原则。

.sync 修饰符目的就是尽可能的在语意上进行规范,让数据流向更加直观易懂。 它分别从两个方面来规范数据的双向绑定:

  1. 对子组件内部触发事件的事件名称进行规定,让由内到外的数据流向更清晰(通过事件名即可知道要更新的外部属性是什么)。
  2. 组件使用时用已有的 v-bind 指令来替换 v-model 指令,减少心智负担,并让从上到下的数据流向更直观(通过 v-bind.sync 修饰符结合使用,可以很直观的说明当前的属性不仅会传递数据,还会被子组件同步修改)。

推荐以 update:myPropName 的格式触发事件。

Vue.component('base-input', {
    props:['value'],
    template:`<input type="text" :value="value" @input="$emit('update:value' $event.target.value)" />`
});

在父组件中使用时,不使用 .sync 修饰符的完整形式:

<base-input v-bind:value="value" v-on:update:value="value=$event" />

使用 .sync 修饰符的方式为:

<base-input :value.sync="value" />

注意:通过 .sync 修饰的 v-bind 指令或者 v-model 的值不能和表达式或函数一起使用,它只能对要绑定的 Prop 进行修改。

.native 修饰符

v-on 指令的 .native 修饰符可以为组件的根元素监听一个原生事件

<base-button @click.navitve="handleClick">click me!</base-button>

需要注意的是,往往自定义组件内部的 DOM 结构与渲染后呈现的 UI 并不一致。为了解决这个问题,我们可以使用 $listeners 实例属性,来跨 DOM 层级进行事件绑定。

$listeners 实例属性

使用 v-on="$listeners" 的方式我们可以跨 DOM 层级为指定的目标元素绑定所有事件。这位编写高级组件提供了便利与基础。

注意 $listeners 实例属性中并不会保存带有 .native 修饰符的原生事件。

Vue.component('base-input', {
  inheritAttrs: false,
  props: ['label', 'value'],
  computed: {
    inputListeners: function () {
      var vm = this
      // `Object.assign` 将所有的对象合并为一个新对象
      return Object.assign({},
        // 我们从父级添加所有的监听器
        this.$listeners,
        // 然后我们添加自定义监听器,
        // 或覆写一些监听器的行为
        {
          // 这里确保组件配合 `v-model` 的工作
          input: function (event) {
            vm.$emit('input', event.target.value)
          }
        }
      )
    }
  },
  template: `
    <label>
      {{ label }}
      <input
        v-bind="$attrs"
        v-bind:value="value"
        v-on="inputListeners"
      >
    </label>
  `
})

现在 <base-input> 组件是一个完全透明的包裹器了,也就是说它可以完全像一个普通的 <input> 元素一样使用。

推荐文章