1

自定义事件

自定义事件有能力使子组件事件触发父组件中的动作。
那么父组件如何监听事件呢?可以使用指令v-on:event-name="callback"监听。
而子组件又是如何触发事件的呢? 很简单,调用this.$emit(eventName)
先来个简单例子

new Vue({
    el:'#app-1',
    methods:{
        callback:function(){
            alert('父组件监听到事件触发,执行回调。')
        }
    },
    components: {
        "component-1":{
            template:'<button v-on:click="emitor" class="btn btn-info">\
                        子组件模板:点击触发自定义事件</button>',
            methods:{
                emitor:function(){
                    console.log('111')
                    this.$emit('alert-event')
                }
            }
        }
    }
})
<div id="app-1">
    <component-1 v-on:alert-event="callback"></component-1>
</div>

根据以上代码,可以得出个事件执行流程,基本上不管什么自定义事件都是这个流程

  • 子组件某方法
  • this.$emit('event')
  • DOM上v-on监听event
  • 父组件methods中的callback

clipboard.png

clipboard.png

子组件已经和它外部完全解耦了。它所做的只是报告自己的内部事件,因为父组件可能会关心这些事件。

事件负载

在事件执行的同时,子组件还可以在触发事件的同时挂上一些负载的数据,随着事件传递给父组件。
使用API的第二个参数this.$emit(event, payload),具体如下。

new Vue({
    el: '#app-2',
    data: {
        message: ''
    },
    methods: {
        handleMessage: function (payload) {
            this.message = payload.message
        }
    },
    components: {
        "component-2": {
            data: function () {
                return { message: '' }
            },
            template: '\
            <div>\
                <input type="text" v-model="message" class="form-control w-25 mt-2">\
                <button @click="sendMsg" class="btn btn-info mt-2">发送</button>\
            </div>',
            methods: {
                sendMsg: function () {
                    this.$emit('message', { message: this.message })
                }
            }
        }
    }
})
<div id="app-2">
    <input v-model="message" class="form-control w-25">
    <component-2 @message="handleMessage"></component-2>
</div>

clipboard.png

点击发送按钮触发事件并把信息传递给父组件,可以看到还是这里同样遵循自定义事件流程,其他都是烟雾,之多了个负载this.$emit('message', { message: this.message })

原生事件

通过.native后缀还可以在子组件元素根节点上绑定原生事件的监听。

new Vue({
    el:'#app-3',
    methods:{
        todo:function(){
            alert('事件回调方法是父组件中的')
        }
    },
    components:{
        "component-3":{
            template:'<button class="btn btn-info mt-2">原生事件监听</button>'
        }
    }
})
<div id="app-3">
    <component-3 @click.native="todo"></component-3>
</div>

还是要千万注意,回调方法在父组件作用域上。

双向绑定(.sync修饰符与update事件)

可以通过以下步骤实现一个双向绑定:

  • 在子组件VirtualDOM节点上,通过v-bind:prop.sync="foo"进行数据下发,这里以.sync修饰符标注,通知父组件子组件需要进行props的更新。
  • 子组件通过this.$emit("update:prop",newValue)通知父组件自己需要把prop更新为一个新值newValue以子组件的data选项作为过渡变量
  • 父组件会监听这个更新事件,从而在负载上拿到newValue,更新foo的数据,并把新数据重新下发给子组件的prop
注意:这里并不是子组件props值的改变引发父组件数据的改变,而是利用子组件的data做桥梁,通过事件及其负载引起父组件的变动。
new Vue({
    el: '#app-4',
    data: { parentMsg: 'parent\'s message' },
    components: {
        "component-4": {
            props: ['child_msg'],
            data: function () {
                return { inputText: this.child_msg }
            },
            template: '\
                <div>\
                    <input v-model="inputText" class="form-control form-control-sm w-25" type="text">\
                </div>',
            watch: {
                child_msg: function (val) {
                    this.inputText = val
                },
                inputText: function (val) {
                    this.$emit('update:child_msg', val)
                }
            }
        }
    }
})
<div id="app-4">
    <input v-model="parentMsg" class="form-control form-control-sm w-25" type="text">
    <component-4 :child_msg.sync="parentMsg" class="mt-2"></component-4>
</div>

clipboard.png

clipboard.png

父、子组件各包含一个输入框,并且将它们绑定到自己的某data属性上。watch子组件的该data属性,一有输入就触发事件通知父组件,并payload新值。父组件通过payload更新自己的data,并通过prop将新值下发给子组件,子组件watch自己的propprop一旦变动,将新变动赋给data

理解v-model

在input元素上使用

我们在用输入框时,会用v-model进行双向绑定。

<input v-mode="message">
等价于
<input :value="message" @input="message = $event.target.value" />

具体行为:① 在inputvalue属性上引用组件的data ② 发生oninput事件时,更新组件data,从而更新value

new Vue({
    el:'#app-6',
    data:{
        message:"Hello"
    }
})
<div id="app-6">
    <input v-model="message"/>
    <input :value="message" @input="message = $event.target.value" />
    <div>{{message}}</div>
</div>

在自定义事件的表单输入组件上使用

前提:
<myComponent v-model="price">
等价于
<myComponent :value="price" @input="price = arguments[0]" >
new Vue({
    el: '#app-7',
    data: {
        price: 100
    },
    components: {
        "component-7": {
            props: ['value'],
            template: '\
                <div>\
                    <input :value="value" @input="updateValue($event.target.value)" class="form-control form-control-sm w-25">\
                    <slot></slot>\
                    <div><span class="badge badge-info">子组件Prop:【{{value}}】</span></div>\
                </div>',
            methods:{
                updateValue:function(value){
                    value = value+"-"
                    this.$emit('input',value)
                }
            }
        }
    },
    
})
<div id="app-7">
    <!-- <component-7 v-model="price"></component-7> -->
    <component-7 :value="price" @input="price = arguments[0]" >
        <div>
            <span class="badge badge-info">父组件数据:【{{price}}】</span>
        </div>
    </component-7>
</div>

clipboard.png

这里的流程,输入框输入时,触发子组件上input事件并执行updateValue方法,方法参数为输入框中的value(通过$emit.target.value获取),方法可以先对value进行一系列加工处理形成super_value,最后使用this.$emit('input',super_value)触发父组件在子组件节点上监听的input事件,并将加工过的super_value负载在事件上。父组件@input="price = arguments[0]"中的arguments[0]即是这个super_value,父组件通过input的回调更新自己的data,在将data下发给子组件的value特性。

这里如果使用v-model指令,那么子组件特性value、父组件监听的事件input及其回调price = arguments[0], 这些都是固定的,不能变化。

此示例的执行流程其实与双向绑定(.sync修饰符与update事件)中的例子是一样的。
只是这里父组件上的input事件功效没有双向绑定中update事件那么强大。

在组件上使用

在输入框组件中已经说过v-model的种种限制,其中最主要的两点,下发的组件特性必须命名为value和父组件监听的只能绑定事件input,不灵活, 例如在checkbox中,我要给下发的特性取名为checked代替value,并且父组件不想监听@input事件,而是@change事件。

为了解决这个不灵活的问题,可以在组件model选项设置propevent,如下

new Vue({
    el: '#app-9',
    data: {
        isChecked: false,
        message:'please choose this box'
    },
    components: {
        'component-9': {
            model: {
                prop: 'checked',
                event: 'change'
            },
            props:{
                checked:Boolean,
                value:String
            },
            template: '<div><input type="checkbox" @click="choose($event.target.checked)"><slot></slot></div>',
            methods:{
                choose:function(checked){
                    this.$emit('change',checked)
                }
            }
        }
    }
})
<div id="app-9" style="height:500px">
    <component-9 v-model="isChecked" v-bind:value="message">
        <span class="badge badge-info">特性`value`现在从v-model绑定中解放出来了,可自定义使用</span>
    </component-9>
</div>

clipboard.png


yanniecheung
70 声望6 粉丝