likes
comments
collection
share

Vue父子组件通信

作者站长头像
站长
· 阅读数 47

在vue项目中,父子组件之间的通信很常见也很重要,父组件通过props将数据传递给子组件,子组件通过触发自定义事件将数据传递给父组件。

一、父子组件之间通信

我们先来看一个简单的例子(vue官网的例子):

背景:创建一个博文组件,组件包含标题、正文和放大博文字号按钮。我们希望能够通过向组件中传入标题和正文来创建一个博文组件,并能够点击按钮放大博文的字号,但要保证页面其他地方的字号不变。

可以直接使用props将标题、正文数据传入组件中,这样我们实现了父组件向子组件的数据传递。当我们点击子组件的放大按钮时,希望将字号放大数值传递给父组件,并由父组件执行放大字号的操作(当然在子组件中可以直接实现字号放大,但是这里是想展示子组件的数据传递给父组件)。

本文没有详细介绍props,如果你想详细了解prop类型、验证等知识,传送门:Prop

代码: 父组件中

  • 父组件中,在使用子组件时,通过@enlarge-text="onEnlargeText"来监听子组件抛出的enlarge-text事件,一旦监听到该事件,即执行onEnlargeText方法改变字号

  • @是v-on:的缩写,@后的内容enlarge-text表示监听的子组件的自定义事件名,该名称必须和子组件中定义的事件名保持一致,推荐使用短横线分隔命名

  • 父组件上的attribute(title、content)绑定的数据可以传到子组件,只要我们在子组件中使用props接受

<template>
  <div class="event_custom">
    <blog-post
      @enlarge-text="onEnlargeText"
      :title="title"
      :content="content"
      :style="{ fontSize: postFontSize + 'px' }"
    ></blog-post>
  </div>
</template>

<script>
import BlogPost from "./EventCustomChild.vue";

export default {
  components: {
    BlogPost,
  },
  data() {
    return {
      title: "标题",
      content:
        "正文正文正文正文正文正文正文正文",
      postFontSize: 16,
    };
  },
  methods: {
    onEnlargeText(enlargeAmount) {
      this.postFontSize += enlargeAmount;
    },
  },
};
</script>

代码:子组件

  • 在子组件的props中,接受父组件传递的数据title和content

  • 在子组件的button按钮上绑定了点击事件,一旦点击按钮,会调用enlargeText方法,执行$emit方法

  • $emit方法第一个参数为自定义事件名,由于v-on事件监听器在dom模板中会自动转换为全小写,所以v-on:myEvent会变成v-on:myevent,如果为自定义事件命名为myEvent,则父组件将监听不到自定义事件,因此我们最好使用kebab-case命名

  • $emit方法第一个参数之后的参数,可以是一个/多个,可以是字符串、数组、对象、函数等。参数值将会传递给监听器回调,父组件监听到事件后会调用onEnlargeText方法,该方法的参数即为自定义事件抛出的参数。 Vue父子组件通信

<template>
  <div class="blog-post">
    <div class="title">{{ title }}</div>
    <div class="content">{{ content }}</div>
    <button @click="enlargeText">放大字号</button>
  </div>
</template>

<script>
export default {
  props: {
    title: {
      type: String,
      default: "",
    },
    content: {
      type: String,
      default: "",
    },
  },
  data() {
    return {};
  },
  methods: {
    // 点击按钮调用这个方法,通过$emit方法,并传入事件名和参数(可选),来触发事件enlarge-text
    // 这个事件会被父组件监听到,前提是我们触发了事件,并在父组件中配置了事件监听
    enlargeText() {
      this.$emit("enlarge-text", 1);
    },
  },
};
</script>

二、自定义组件实现V-model

v-model本质是语法糖,下面两行代码是等价的,通过input事件监听输入框中数据的变化,并将输入框中的value值与变量name绑定,因此输入框中的数据发生变化,name会发生变化,页面中使用name的地方也会相应改变。

<input v-model="name">
<input v-bind:value="name" @input="name = $event.target.value">

在父子组件通信中,父组件通过v-bind绑定向子组件传递的数据,监听子组件的自定义事件,v-model既绑定了数据,又监听了事件,我们可以通过自定义组件实现v-model。

代码:父组件(子组件命名为vInput)

  • 子组件的props通过value属性获取父组件的name数据(v-bind:value="name")

  • 父组件监听子组件通过$emit方法抛出的input事件,并将值赋值给name

<template>
  <div class="event_custom">
    <vInput v-model="name"></vInput>
    <div>{{ name }}</div>
  </div>
</template>

<script>
import vInput from "./EventCustomChild.vue";

export default {
  components: {
    vInput,
  },
  data() {
    return {
      name: "",
    };
  },
};
</script>

代码:子组件

<template>
  <div class="input">
    <input type="text" :value="value" @input="onInput" />
  </div>
</template>

<script>
export default {
  props: ["value"],
  methods: {
    onInput(event) {
      this.$emit("input", event.target.value);
    },
  },
};
</script>

一个组件上的 v-model 默认会利用名为 value 的 prop 和名为 input 的事件,但是像单选框、复选框等类型的输入控件可能会将 value attribute 用于不同的目的——vue官网。

v-model默认props名为value,事件为input事件。如果我们定义组件时想使用props的名为checked,事件为change,vue提供了一个model选项。在model选项中,我们定义了prop的值为checked,event的值为change,那么相当于

<input v-model="isChecked">
<input v-bind:checked="isChecked" @change="isChecked = $event.target.checked">
<template>
  <div class="event_custom">
    <checkbox v-model="isChecked"></checkbox>
    <div>isChecked的值为:{{ isChecked }}</div>
  </div>
</template>

<script>
import checkbox from "./EventCustomChild.vue";

export default {
  components: {
    checkbox,
  },
  data() {
    return {
      isChecked: false,
    };
  },
};
</script>
<template>
  <div class="input">
    <input type="checkbox" :checked="checked" @change="onChange" />
  </div>
</template>

<script>
export default {
  model: {
    prop: "checked",
    event: "change",
  },
  props: {
    checked: Boolean,
  },
  methods: {
    onChange(event) {
      this.$emit("change", event.target.checked);
    },
  },
};
</script>

三、.sync

.sync也是一个语法糖

vue自动为父组件创建一个事件监听器,用来改变父组件的值,事件的名为update:prop名。假如prop名为title,那么事件名为update:title,事件的目的是为了更新title的值

<text-document :title.sync="text"></text-document>
//相当于
<text-document :title="text" @update:title="newTitle=>text=newTitle"></text-document>

this.$emit("update:title", newTitle);

代码:父组件

<template>
  <div class="event_custom">
    <textDecoration :title.sync="text"></textDecoration>
    <div>text: {{ text }}</div>
  </div>
</template>

<script>
import textDecoration from "./EventCustomChild.vue";

export default {
  components: {
    textDecoration,
  },
  data() {
    return {
      text: "1",
    };
  },
};
</script>

代码:子组件

<template>
  <div class="input">
    <div @click="handleTitle">在子组件中点击改变title值</div>
  </div>
</template>

<script>
export default {
  props: ["title"],
  methods: {
    handleTitle() {
      this.$emit("update:title", "123");
    },
  },
};
</script>

注意带有 .sync 修饰符的 v-bind 不能和表达式一起使用 (例如 v-bind:title.sync=”doc.title + ‘!’” 是无效的)。取而代之的是,你只能提供你想要绑定的 property 名,类似 v-model

将 v-bind.sync 用在一个字面量的对象上,例如 v-bind.sync=”{ title: doc.title }”,是无法正常工作的,因为在解析一个像这样的复杂表达式的时候,有很多边缘情况需要考虑。

四、$attrs和$listeners

$attrs:包含了父作用域中不作为 prop 被识别 (且获取) 的 attribute 绑定 (class 和 style 除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 (class 和 style 除外),并且可以通过 v-bind="$attrs" 传入内部组件——在创建高级别的组件时非常有用。 ————vue官网

$listeners:包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on="$listeners" 传入内部组件——在创建更高层次的组件时非常有用。 —vue官网

inheritAttrs:默认情况下父作用域的不被认作 props 的 attribute 绑定 (attribute bindings) 将会“回退”且作为普通的 HTML attribute 应用在子组件的根元素上。也就是说,父组件中没有使用props接受的attribute会显示在子组件的根元素上。

简单地说:通过子组件作为中间层,孙子组件可以拿到父组件的数据,触发父组件的监听器

我们通过代码理解一下:

代码:父组件

<template>
  <div class="father">
    <div>爸爸</div>
    <son
      :text="text"          //父组件向子组件传入textmsgcontent
      :msg="msg"
      :content="content"
      @handle1="handle1"  //父组件中定义事件监听器,监听handle1handle2事件
      @handle2="handle2"
    ></son>
  </div>
</template>

<script>
import son from "./EventCustomChild.vue";

export default {
  components: {
    son,
  },
  data() {
    return {
      text: "1",
      msg: "12",
      content: "123",
    };
  },
  methods: {
    handle1() {
      console.log("父组件的handle1函数");
    },
    handle2() {
      console.log("父组件的handle2函数");
    },
  },
};
</script>

代码: 子组件

<template>
  <div class="son">
    <div @click="handle">子组件</div>
    <grandson v-bind="$attrs" v-on="$listeners" @handle1="handle1"></grandson>
    //子组件中通过 v-bind="$attrs" v-on="$listeners" 将父组件的数据和监听器传递给孙子组件
    //子组件中定义事件监听器,监听handle1事件
  </div>
</template>

<script>
import grandson from "./EventCustomChildChild.vue";

export default {
  inheritAttrs: false,//为true,子组件的根元素为<div class="son" msg="12" content="123"> 没有被props接收的attribute会显示
                      //为false,子组件的根元素为<div class="son">
  components: {
    grandson,
  },
  props: ["text"],
  methods: {
    handle() {
      console.log(this.$attrs);//{msg: '12', content: '123'},获取父组件传过来,没有被props接收
      console.log(this.$listeners);   //{handle1: ƒ, handle2: ƒ},获取父作用域中的(不含 `.native` 修饰器的) `v-on` 事件监听器
    },
    handle1() {
      console.log("儿子组件中的handle1");
    },
  },
};
</script>

代码:孙子组件

<template>
  <div class="grandson">
    <div @click="handle">孙子组件</div>
  </div>
</template>

<script>
export default {
  data() {
    return {};
  },
  props: ["msg"],
  methods: {
    handle() {
      console.log(this.$attrs);//{content: '123'},子组件通过v-bind="$attrs"将非props传递到孙子组件
      //如果没有使用props,结果为{msg: '12', content: '123'},但如果调用孙子组件时,发生下述情况,msg值会被覆盖,结果变为{msg: '11111', content: '123'}
      //<grandson v-bind="$attrs" v-on="$listeners" @handle1="handle1" :msg="11111"></grandson>
      console.log(this.$listeners);//{handle1: ƒ, handle2: ƒ},获取上级组件的所有(不含 `.native` 修饰器的) `v-on` 事件监听器
      //子组件和父组件都定义了handle1事件监听器,但他们不会覆盖,展开handle1,会发现里面包含两个函数
      this.$emit("handle1");//儿子组件中的handle1、父组件的handle1函数,先触发子组件,再触发父组件
      this.$emit("handle2");//父组件的handle2函数
    },
  },
};
</script>

我们可以使用$attrs和$listeners封装第三方插件,我们希望二次封装的插件不仅支持自己定义的属性,也支持第三方插件原有的属性,使用$attrs和$listeners可以直接把第三方插件的属性和监听器传递给用户,否则我们可能要使用props接收第三方插件的属性,并一个一个向外传递。

转载自:https://juejin.cn/post/7124568391326629901
评论
请登录