接着前面的内容:https://www.cnblogs.com/yanggb/p/12609450.html。
组件的自定义事件
这里来学习一下组件中的自定义事件。
事件名
不同于组件名和prop,事件名不存在任何自动化的大小写转化,而是触发的事件名需要完全匹配监听这个事件所用的名称。
举个例子,如果触发一个camelCase名字的事件:
this.$emit('myEvent')
则监听这个名字的kebab-case版本是不会有任何效果的:
<!-- 没有效果 --> <my-component v-on:my-event="doSomething"></my-component>
这是因为,不同于组件名和prop,事件名并不会被用作以一个javascript变量名或者属性名,所以就没有理由使用camelCase或PascalCase了。并且,由于【v-on】事件监听器在dom模板中会被自动转换为小写(因为html是大小写不敏感的),即【v-on:myEvent】会变成【v-on:myevent】,也就导致了myEvent不可能被监听到。
因此官方文档的建议是始终使用kebab-case的事件名,使用kebab-case的事件名,使用kebab-case的事件名。
自定义组件的【v-model】
一个组件上的【v-model】指令默认会利用名为【value】的prop和名为【input】的事件,但是像单选框或复选框等特殊类型的输入控件,则可能会将value属性用于不同的目的。因此,vue在2.2.0+的版本中提供了【model】选项,用来避免这样的冲突。
Vue.component('base-checkbox', { model: { prop: 'checked', event: 'change' }, props: { checked: Boolean }, template: ` <input type="checkbox" v-bind:checked="checked" v-on:change="$emit('change', $event.target.checked)"> ` })
<base-checkbox v-model="lovingVue"></base-checkbox>
在上面的组件定义中,使用了model选项来声明该组件是利用的【checked】的prop和名为【change】的事件,并通过【v-bind:checked】和【v-on:change】来绑定值和事件。因此在后面的组件实例中,lovingVue的值就会被传入到这个名为checked的prop中。同时,当<base-checkbox>触发一个change事件并附带一个新的值得时候,这个lovingVue的属性也会被同步更新。另外要注意的是,你仍然需要在组件的props选项中声明checked这个prop。
将原生的事件绑定到组件中
你可能会有很多次想要在一个组件的根元素上直接监听一个原生的事件。这时,你可以使用【v-on】指令的【.native】修饰符。
<base-input v-on:focus.native="onFocus"></base-input>
这在有的时候是很有用的,不过在你尝试监听一个类似<input>的非常特定的元素的时候,这就并不是一个好的主意。比如上述<base-input>组件可能做了如下重构,所以根元素实际上是一个<label>元素:
<label> {{ label }} <input v-bind="$attrs" v-bind:value="value" v-on:input="$emit('input', $event.target.value)"> </label>
这时候,父级的【.native】监听器将静默失败,并不会产生任何的报错,但是onFocus处理函数却不会被调用,因为<label>元素并没有原生的onfocus事件。
为了解决这个问题,vue提供了一个【$listeners】属性,它是一个对象,里面包含了作用在这个组件上的所有监听器,例如:
{ focus: function (event) { /* ... */ } input: function (value) { /* ... */ }, }
而有了这个【$listeners】属性,你就可以配合【v-on="$listeners"】指令将所有的事件监听器都指向这个组件的某个特定的子元素。
另外,对于类似<input>的你希望它也可以配合【v-model】指令工作的组件(在【v-model】指令的基础上加以触发另外的事件)来说,为这些监听器创建一个类似下述inputLiseners的计算属性通常是非常有用的:
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>元素一样使用:所有跟它相同的的attribute和监听器都可以正常工作。
【.sync】修饰符
在有些情况下,我们可能需要对一个prop进行双向绑定。而不幸的是,真正的双向绑定会带来维护上的问题,因为子组件可以修改父组件,且在父组件和子组件都没有明显的改动来源。这是官方文档推荐以【update:myPropName】的模式触发事件的原因。举个例子,在一个包含title属性的假设组件中,我们可以使用以下方法表达对其赋值的意图:
this.$emit('update:title', newTitle)
然后父组件就可以监听那个事件,并根据需要更新一个本地的数据属性,例如:
<text-document v-bind:title="doc.title" v-on:update:title="doc.title = $event"></text-document>
而vue在2.3.0+的版本中新增了【.sync】修饰符作为这种模式的缩写,以此来方便开发,减少代码量。
<text-document v-bind:title.sync="doc.title"></text-document>
这里要注意的是,带有【.sync】修饰符的【v-bind】指令不能和表达式一起使用(例如【v-bind:title.sync="doc.title + '!'"】就是无效的)。取而代之的你只能提供想要的属性名,类似【v-model】。
而当我们用一个对象同时设置多个prop的时候,也可以将这个【.sync】修饰符和【v-bind】指令配合使用。
<text-document v-bind.sync="doc"></text-document>
这样就会把doc对象中的每一个属性(如title)都作为一个独立的prop传进去,然后各自添加用于更新的【v-on】监听器。
此外要注意的是,将【v-bind.sync】用于一个字面量的对象上,例如【v-bind.sync="{ title:doc.title }"】是无法正常工作的,因为在解析这样一个复杂的表达式的时候,有很多的边缘情况需要考虑。
"我还是很喜欢你,像星辰闪耀苍穹顶,不惧孤寂。"