×

Vue 父子组件通信:子组件如何通知父组件更新数据?

独孤求败 独孤求败 发表于2026-06-11 09:04:42 浏览5 评论0

抢沙发发表评论

我们知道,Props 实现的父子组件数据传递遵循单向数据流原则,子组件不可以擅自更改父组件传过来的值。

那么,如果子组件确实需要更新这些数据该怎么办呢?

此时就需要使用 emit 自定义事件。子组件通过 emit 向父组件发送通知,由父组件负责修改数据,再将最新的数据通过 Props 传递给子组件。整个过程遵循 “谁拥有数据,谁负责修改数据” 的设计原则。

具体流程如下:

  • • 父组件:「有事就告诉我。」(监听事件)
  • • 子组件:「数据需要更新啦!」(emit 触发事件并传递数据)
  • • 父组件:「收到,我来修改数据。」(更新 data
  • • 子组件:「拿到最新数据了。」(通过 Props 重新接收数据,如果有Props数据会重传给子组件)

emit简单例子

我们通过一个最简单的示例来演示子组件如何通过 emit 事件通知父组件更新数据。这里仅展示 emit 的基本用法,实际开发中通常会结合 props 和 emit 一起使用,以实现更完整的父子组件通信。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>简单 Emit 示例</title>
    <script src="https://unpkg.com/vue@3.5.17/dist/vue.global.js"></script>
</head>
<body>
    <div id="root"></div>
</body>

<script>
    // 1. 父组件
    const vm = Vue.createApp({
        data() {
            return {
                message'等待点击...'// 用来接收子组件传来的字
            }
        },
        methods: {
            // 这里的 text 就是子组件传上来的数据
            reply(text) {
                this.message = text;
            }
        },
        // @say="reply" 意思是:当子组件触发 say 事件时,执行父组件的 reply 方法
        template`
            <div>
                <h2>父组件收到:{{ message }}</h2>
                <hr />
                <my-button @say="reply"></my-button>
            </div>
        `

    });

    // 2. 子组件
    vm.component('my-button', {
        // 声明组件触发的自定义事件(Vue3 推荐的做法,利于代码可读性)
        emits: ['say'], 
        methods: {
            send() {
                // 触发 say 事件,并顺便捎带一句话 "来自子组件的问候!"
                this.$emit('say''来自子组件的问候!');
            }
        },
        template'<button @click="send">点击通知父组件</button>'
    });

    vm.mount('#root');
</script>
</html>

如上面的例子所示,我们做了如下三个步骤:

  1. 1. 父组件通过 v-on(或 @)监听子组件触发的自定义事件,并在事件触发时执行对应的方法。
<my-button @say="reply"></my-button>
  1. 2. 子组件在需要的时候使用 this.$emit 通知父组件进行更新。
this.$emit('say', '来自子组件的问候!');
  1. 3. 父组件收到通知后,会执行对应的事件处理方法
 methods: {
      // 这里的 text 就是子组件传上来的数据
      reply(text) {
          this.message = text;
      }
  },

事件校验

和 Props 校验类似,我们也可以对 emit 事件进行校验,下面来看一个例子。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Vue3 Emit 校验示例</title>
    <script src="https://unpkg.com/vue@3.5.17/dist/vue.global.js"></script>
</head>
<body>
    <div id="root"></div>
</body>

<script>
    // 1. 父组件
    const vm = Vue.createApp({
        data() {
            return {
                message'等待点击...',
                code0
            }
        },
        methods: {
            // 接收子组件传来的多个参数
            reply(text, status) {
                this.message = text;
                this.code = status;
            }
        },
        template`
            <div>
                <h2>父组件收到:{{ message }} (状态码: {{ code }})</h2>
                <hr />
                <my-button @say="reply"></my-button>
            </div>
        `

    });

    // 2. 子组件
    vm.component('my-button', {
        // 【核心改变】:将 emits 改为对象形式,并为 say 事件编写校验函数
        emits: {
            // say 对应一个函数,函数的参数就是 this.$emit 传出来的参数
            say(text, status) => {
                // 校验逻辑:必须传 text,且 status 必须是 200 或者是 404
                const isValid = text && [200404].includes(status);
                
                if (!isValid) {
                    console.warn(`❌ [Emit 校验失败]: 传递的参数非法!当前收到 text: ${text}, status: ${status}`);
                }
                
                // 必须返回一个布尔值:true 代表校验通过,false 代表校验失败(控制台会报警告)
                return isValid; 
            }
        }, 
        methods: {
            sendSuccess() {
                // 触发 say 事件,传过去的数据符合校验规则 (status 是 200)
                this.$emit('say''数据加载成功!'200);
            },
            sendError() {
                // 触发 say 事件,传过去的数据不符合校验规则 (status 是 500,不在 [200, 404] 里面)
                this.$emit('say''服务器崩溃啦!'500);
            }
        },
        template`
            <div>
                <button @click="sendSuccess">发送正确数据 (200)</button>
                <button @click="sendError" style="margin-left: 10px;">发送错误数据 (500)</button>
            </div>
        `

    });

    vm.mount('#root');
</script>
</html>

在这个例子中,say 被定义为一个函数,当校验不通过时返回 false,校验通过时返回 true

注意:事件校验和 Props 校验一样,并不是强制性的,只会在浏览器控制台给出警告,开发者应及时修复这些警告。

更优雅的写法

看下面这个例子

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>v-model 改写示例</title>
    <script src="https://unpkg.com/vue@3.5.17/dist/vue.global.js"></script>
</head>
<body>
    <div id="root"></div>
</body>

<script>
    // 1. 父组件
    const vm = Vue.createApp({
        data() {
            return {
                message'等待点击...'// 这个数据将与子组件双向绑定
            }
        },
        // 使用 v-model:model-value(简写为 v-model)直接绑定 message
        // 不需要再手动写 @say="reply" 和 methods 了
        template`
            <div>
                <h2>父组件收到:{{ message }}</h2>
                <hr />
                <my-button v-model="message"></my-button>
            </div>
        `

    });

    // 2. 子组件
    vm.component('my-button', {
        // Vue3 中默认的 v-model 接收的属性名是 modelValue
        props: ['modelValue'],
        // 对应的,声明的触发事件必须是 'update:modelValue'
        emits: ['update:modelValue'], 
        methods: {
            send() {
                // 触发 update:modelValue 事件,直接修改父组件绑定的变量
                this.$emit('update:modelValue''来自子组件的问候!');
            }
        },
        template'<button @click="send">点击通知父组件</button>'
    });

    vm.mount('#root');
</script>
</html>

我们没有按照上面“三步走”的方式实现,但发现组件内的 message 仍然会被更新,这说明该例子是有效的,而且写法也更加优雅。

这里我们用到了 v-modelv-model 本质上是一个语法糖,它在底层自动帮我们处理了属性绑定(Props)和事件监听(Emits)。接下来我们看看,相比之前的写法,这个例子发生了哪些变化。

  1. 1. 父组件视角: 使用的是 v-model,而不是手动的 v-bind 绑定。
  2. 2. 子组件的接收:在 Vue 3 中,v-model 会被默认拆解,子组件需要通过 props: ['modelValue'] 来接收传递进来的值。
  3. 3. 子组件的更新:当点击子组件时,执行 this.$emit('update:modelValue', 新值)。其中 update:modelValue 是 Vue 约定的特殊事件名。父组件监听到该事件后,会自动将子组件传递的新值更新到 myCount 变量中。

你可能会觉得 modelValue / update:modelValue 这两个名字比较固定,因为它们是 Vue 3 的默认约定。如果不想使用默认命名,也可以进行自定义重命名。

父组件

<my-button v-model:message="message"></my-button>

子组件

vm.component('my-button', {
    // Vue3 中默认的 v-model 接收的属性名是 modelValue
    props: ['message'],
    // 对应的,声明的触发事件必须是 'update:modelValue'
    emits: ['update:message'], 
    methods: {
        send() {
            // 触发 update:modelValue 事件,直接修改父组件绑定的变量
            this.$emit('update:message''来自子组件的问候!');
        }
    },
    template'<button @click="send">点击通知父组件</button>'
});


群贤毕至

访客