Vue 组件通信:父子组件之间的数据传递和事件监听


vue-communication

组件内事件监听

在单个 Vue 组件内部实现事件监听和处理和在全局 Vue 对象实例中的操作并没有什么不同:

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Vue 组件通信</title>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.5.21/dist/vue.js"></script>
</head>
<body>
    <div id="app">
        <v-button></v-button>
    </div>
    <script>
        Vue.component('v-button', {
            template: '<button @click="alertText">添加</button>',
            methods: {
                alertText() {
                    alert('点击了添加按钮')
                }
            }
        })
        var app = new Vue({
            el: '#app'
        })
    </script>
</body>
</html>

我们在浏览器中预览该文档,点击添加按钮,就可以看到弹出的消息框:

-w734

父子组件间事件监听和数据传递

如果是更复杂的嵌套组件的话,有的时候我们可能想要子组件的事件被父组件监听并处理,比如前面编写的 Web 开发框架列表示例

Web 开发框架列表

可以将该视图中的列表渲染和添加功能分离,将添加功能通过子组件完成(后续还可以实现该列表的删除功能子组件),这样,每次添加新的框架后,就会触发父组件的监听函数执行添加功能。

要实现这个子组件事件被父组件监听的功能,需要用到 Vue.js 框架的自定义事件功能:我们可以在父组件中监听一个自定义的事件并编写对应的事件监听函数,再在子组件中监听某个系统事件,最后通过 Vue 内置的组件通知机制将子组件的事件绑定到父组件事件,这样子组件上的事件就可以被父组件监听和处理了。

看文字可能有点云里雾里,我们下面编写一段示例代码来演示,还是以 Web 开发框架列表为例,这一次,我们将其重构为通过组件嵌套的方式来实现,在 vue_learning/component 目录下新建一个 event.html 文件,编写对应的实现代码如下:

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>通过 Vue 组件通信实现父组件监听并处理子组件事件</title>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.5.21/dist/vue.js"></script>
</head>
<body>
<div id="app">
    <p>Web开发框架:</p>
    <web-framework-list></web-framework-list>
</div>
<script>
    Vue.component('web-framework-list', {
        data: function () {
            return {
                frameworks: [
                    {'name': 'Laravel', 'language': 'php'},
                    {'name': 'Vue', 'language': 'javascript'},
                    {'name': 'Gin', 'language': 'golang'},
                    {'name': 'Flask', 'language': 'python'},
                ],
                languages: [
                    'php', 'golang', 'javascript', 'python'
                ],
            }
        },
        template: '<div>' +
            '<ul>' +
            '<li v-for="framework in sortedFrameworks" :class="framework.language">{{ framework.name }}</li>' +
            '</ul>' +
            '<add-web-framework :frameworks="frameworks" :languages="languages" @framework-added="addFramework"></add-web-framework>' +
            '</div>',
        methods: {
            addFramework(framework, language) {
                this.frameworks.push(
                    {'name': framework, 'language': language}
                );
            }
        },
        computed: {
            sortedFrameworks() {
                return this.frameworks.sort((a, b) => {
                    if (a.language < b.language) {
                        return -1;
                    } else if (a.language > b.language) {
                        return 1;
                    } else {
                        return 0;
                    }
                });
            }
        }
    });
    Vue.component('add-web-framework', {
        props: ['frameworks', 'languages'],
        data: function () {
            return {
                newFramework: '',
                newLanguage: '',
            }
        },
        template: '<div>' +
            '框架:<input v-model="newFramework" name="framework"/> ' +
            '语言: <select v-model="newLanguage"><option v-for="language in languages" v-text="language"></option></select> ' +
            '<button @click="addNewFramework">新增框架</button>' +
            '</div>',
        methods: {
            addNewFramework() {
                let exists = this.frameworks.find(framework => framework.name == this.newFramework);
                if (exists) {
                    alert('该框架已存在!');
                    return;
                }
                this.$emit('framework-added', this.newFramework, this.newLanguage);
            }
        }
    });
    var app = new Vue({
        el: '#app'
    });
</script>
</body>
</html>

可以看到,我们通过自定义的 web-framework-list 组件实现 Web 框架列表的渲染,并且在这个组件内又通过 add-web-framework 子组件实现新增框架功能,这里我们在父组件中注册子组件时,自定义了一个 framework-added 事件:

<add-web-framework :frameworks="frameworks" :languages="languages" @framework-added="addFramework"></add-web-framework>

用来监听从 add-web-framework 子组件中的按钮点击事件:

<button @click="addNewFramework">新增框架</button>

在子组件中,触发按钮 click 事件时,会通过如下代码将该事件转发给父组件作用域中定义的 framework-added 事件监听函数处理:

addNewFramework() {
    this.$emit('framework-added', this.newFramework, this.newLanguage);
}

可以看到,子组件可以通过 $emit 函数向父组件发送事件消息,并且支持传递参数,父组件中自定义的 framework-added 事件对应的处理函数是 addFramework

addFramework(framework, language) {
    this.frameworks.push(
        {'name': framework, 'language': language}
    );
}

这样,子组件中的按钮点击事件最终会上报到这个 addFramework 函数进行处理,将新增的框架添加到框架列表显示出来。

子组件可以通过 $emit 函数传递事件和数据到父组件,父组件则可以通过 props 机制将数据传递给子组件,在这里示例中,我们也看到对应的应用代码,在父组件中注册子组件时,通过属性绑定将 frameworkslanguages 数据传递给了子组件:

<add-web-framework :frameworks="frameworks" :languages="languages" @framework-added="addFramework"></add-web-framework>

在子组件中,则通过 props 声明了从父组件接收哪些父组件传递的属性数据:

props: ['frameworks', 'languages'],

这样一来,我们就可以直接在子组件中使用这两个属性了:

template: '<div>' +
    '框架:<input v-model="newFramework" name="framework"/> ' +
    '语言: <select v-model="newLanguage"><option v-for="language in languages" v-text="language"></option></select> ' +
    '<button @click="addNewFramework">新增框架</button>' +
    '</div>',
methods: {
    addNewFramework() {
        let exists = this.frameworks.find(framework => framework.name == this.newFramework);
        if (exists) {
            alert('该框架已存在!');
            return;
        }
        this.$emit('framework-added', this.newFramework, this.newLanguage);
    }
}

这样一来,我们就实现在父子组件之间的双向通信了,是不是非常简单方便?

最后,需要注意的是,每个 Vue 组件都应该有且只有一个根元素,所以我们在重构的时候无论是 web-framework-list 还是 add-web-framework 组件都在最外层包裹了 <div> 元素,否则会报错。

我们可以在 Vue Devtools 的 Components 选项卡中看到组件之间的嵌套关系以及组件数据,是模型数据(data)还是来自父组件的 props 数据,抑或是计算属性(computed):

-w1077

还可以在 Events 选项卡中看到所有的 Vue 事件:

-w1065


Vote Vote Cancel Collect Collect Cancel

<< 上一篇: Vue 组件注册:基本使用和组件嵌套

>> 下一篇: Vue 组件插槽:父子组件间的内容分发和插槽作用域