1、监听子组件事件

前面介绍了父组件如何通过 prop 向子组件传递数据,反过来,子组件如何向父组件通信呢?
在 Vue.js 中,这是通过自定义事件来实现的,子组件使用 $emit() 方法触发事件,父组件使用 v-on 指令监听子组件的自定义事件。

// evenName: 事件名
// args: 事件传递的参数
vm.$emit( evenName, [...args] )
<div id="app">
    <child @greet="sayHello"></child>
</div>

<script src="vue.js"></script>
<script>
    Vue.component('child', {
        props: [],
        data() {
            return {
                name: '张三'
            }
        },
        methods: {
            handleChick() {
                this.$emit('greet', this.name)
            }
        },
        template: `<button @click="handleChick">开始欢迎</button>`
    });
    new Vue({
        el: '#app',
        methods: {
            sayHello(name) {
                alert("Hello, " + name)
            }
        }
    });
</script>

1.1、案例:点赞

<div id="app">
    <post-list></post-list>
</div>

<script src="vue.js"></script>
<script>
    // 父组件
    Vue.component('PostList', {
        data() {
            return {
                posts: [
                    {id: 1, title: '《Spring Boot实践》', author: '张三', date: '2019-10-21 20:10:15', vote: 0},
                    {id: 2, title: '《Vue.js入门》', author: '李四', date: '2019-10-10 09:15:11', vote: 0},
                    {id: 3, title: '《Python数据分析》', author: '王五', date: '2019-11-11 15:22:03', vote: 0}
                ]
            }
        },
        methods: {
            // 自定义事件vote的事件处理器方法
            handleVote(post) {
                post.vote = ++post.vote
                return post
            }
        },
        template: `
          <div>
          <ul>
            <PostListItem
                v-for="post in posts"
                :key="post.id"
                :post="post"
                @vote="handleVote(post)"/>
          </ul>
          </div>`
    });

    // 子组件
    Vue.component('PostListItem', {
        methods: {
            handleVote() {
                // 触发自定义事件
                this.$emit('vote');
            }
        },
        props: ['post'],
        template: `
          <li>
          <p>
            <span>标题:{{ post.title }} | 发帖人:{{ post.author }} | 发帖时间:{{ post.date }} | 点赞数:{{ post.vote }}</span>
            <button @click="handleVote">赞</button>
          </p>
          </li>
        `
    });

    let vm = new Vue({
        el: '#app'
    });
</script>

2、将原生事件绑定到组件

在组件上也可以监听原生事件,在使用 v-on 命令时,添加一个 .native 修饰符即可。如:

<base-input @focus.native="onFocus"></base-input>

这种方式最终是在组件的根元素上添加了 focus(聚焦)事件的监听,如果组件模板的根元素是 <input> ,那没有问题,但是如果不是,就有问题了。如:

Vue.component('MyInput', {
    template: `
        <label>
            {{ label }}
            <input class="child">
        </lavel>`
})

根元素是 <label> ,相当于在<label>上添加了 focus 事件监听器,这时,父级的 .native 监听器将静默失败,它不会报错,但是 onFocus 处理函数不会被如期被调用
为了解决这个问题,Vue.js 提供了一个 $listeners 属性,它是一个对象,里面包含了作用在这个组件上的所有监听器,如:

{
    focus(event) {...},
    input(value) {...},
    ...
}

有了 $listeners 属性,就可以使用 v-on="$listeners" 将组件上的所有事件监听器发送到特定的子元素。对于需要那些使用 v-model 的元素(如 <input> )来说,可以为这些监听器创建一个新的计算属性,如下面:

<div id="app">
    <my-input :label="title" v-model="msg" @focus="onFocus"></my-input>
    <p>{{ msg }}</p>
</div>

<script src="vue.js"></script>
<script>
    Vue.component('MyInput', {
        inheritAttrs: false,
        // 父级传入数据:title -> label;msg -> value
        props: ['label', 'value'],
        data() {
            return {}
        },
        computed: {
            inputListeners() {
                let vm = this
                // 将所有的对象合并为一个新对象
                return Object.assign({},
                    // 从父级添加的所有监控
                    this.$listeners,
                    // 添加自定义监控器或覆写一些监听器的行为
                    {
                        // 确保组件和 v-model 一起工作
                        input(event) {
                            vm.$emit('input', event.target.value)
                        }
                    }
                )
            }
        },
        template: `
          <label>
          {{ label }}
          <input
              v-bind="$attrs"
              :value="value"
              v-on="inputListeners">
          </label>`
    });
    new Vue({
        el: '#app',
        data: {
            title: '输入框:',
            msg: '请输入'
        },
        methods: {
            onFocus() {
                console.log("不要摸人家么,好痒,臭流氓!")
            }
        }
    });
</script>

2.1、.sync 修饰符

在某些情况下,可能需要对一个组件的 prop 进行双向绑定,Vue.js 推进以 update: myPropName 模式触发事件来实现。例如:

<div id="app">
    <span>父组件计数值:{{ counter }}</span>
    <!--    <child :val="counter" @update:val="addCounter"></child>-->
    <!--  $event:自定义事件的附加参数  -->
    <child :val="counter" @update:val="counter = $event"></child>
</div>

<script src="vue.js"></script>
<script>
    Vue.component('child', {
        props: {
            val: {
                type: Number,
                default: 0
            }
        },
        data() {
            return {
                count: this.val
            }
        },
        methods: {
            handleChick() {
                this.$emit('update:val', ++this.count)
            }
        },
        template: `
          <div>
          <span>子组件计数值:{{ val }}</span>
          <button @click="handleChick">增加计数</button>
          </div>`
    });
    new Vue({
        el: '#app',
        data: {
            counter: 0
        },
        methods: {
            addCounter(val) {
                return this.counter = val;
            }
        }
    });
</script>

为了方便起见,Vue.js 为了上述这种模式提供了一个缩写,即 .sync 修饰符(在 v-bind 指令上使用),修改如下:

<child :val="counter" @update:val="counter = $event"></child>

<child :val.sync="counter"></child>

当用一个对象同时设置多个 prop 的时候,也可以将 .sync 修饰符和 v-bind 一起使用:

<text-document v-bind.sync="doc"></text-document>

这里会把 doc 对象中的每一个属性作为一个单独的 prop 传进去,然后为每个属性添加 v-on:update 监听器。

<body>
<div id="app">
    <span>父组件 post:{{ post.title }} | {{ post.author }} | {{ post.time }} | {{ post.vote }} | {{ post.price }}</span>
    <child v-bind.sync="post"></child>
</div>

<script src="vue.js"></script>
<script>
    Vue.component('child', {
        props: {
            title: { type: String },
            author: { type: String },
            time: { type: String },
            vote: { type: Number },
            price: { type: Number },
        },
        data() {
            return {
                title: this.vote,
                author: this.vote,
                time: this.vote,
                vote: this.vote,
                price: this.price
            }
        },
        methods: {
            handleChick() {
                this.$emit('update:vote', this.vote += 1)
                this.$emit('update:price', this.price += 4)
            }
        },
        template: `
          <div>
          <span>子组件 post:{{ title }} | {{ author }} | {{ time }} | {{ vote }} | {{ price }}</span>
          <br>
          <button @click="handleChick">增加计数</button>
          </div>`
    });
    new Vue({
        el: '#app',
        data: {
            post: {
                title: '《Spring Boot 从入门到入土》',
                author: '张三',
                time: '2021年05月16日00:05:50',
                vote: 0,
                price: 0
            }
        },
        methods: {}
    });
</script>

注:本篇主要来源:《Vue.js 从入门到实践》第十一章 组件,作者:孙鑫,出版社:中国水利水电出版社

Logo

前往低代码交流专区

更多推荐