Vue.js 组件通信都有哪些?

Vue组件间通讯遵循单项数据流的原则:父组件→子组件。

如果子组件想要更改父组件的数据,需要通知父组件去修改,本质上还是父组件自己去更改数据。

组件间的通信归纳为以下:

目录

Props 父组件 -> 子组件 传参 

$emit 子组件 -> 父组件 传参

$parent / $children 父组件 ⇋ 子组件 传参

$attrs / $listeners 父组件 → 子组件 传参

 $ref 传参(父组件可以在子组件渲染完成后拿到子组件参数)

$root 传参 (根组件向所有子孙组件传参)

 $bus.$on / $emit 传参 (EventBus各组件相互通信)

 Provide & Inject 传参 (各组件相互通信)


Props 父组件 -> 子组件 传参 

页面展示

描述:父组件中子组件<son />绑定了share属性,值为父组件的data:"father_share_data"。同时子组件这边使用props接收属性(参数)share,完成父组件→子组件传参

【父组件.vue 代码】

<template>
  <div id="father">
    <span>[father]</span>
    <son :share="data" />
  </div>
</template>
<script>
import son from "./Son.vue";
export default {
  data() {
    return {
      data: "father_share_data",
    };
  },
  components: {
    son,
  },
};
</script>

【子组件.vue 代码】

<template>
  <div id="son">
    <span>[son]</span>
    <div>{{ share }}</div>
  </div>
</template>
<script>
export default {
  data() {
    return {
      data: 'son_share_data',
    };
  },
  props: ["share"],
};
</script>

$emit 子组件 -> 父组件 传参

页面展示

 描述:在子组件中监听click事件并用$emit方法触发名为share的自定义事件,传参子组件的data值,当点击div元素后,父组件监听名为share的自定义事件,当触发时父组件的data赋值子组件传过来的参数,也就是就是data : "son_share_data"。

【父组件.vue 代码】

<template>
  <div id="father">
    <div>[father]</div>
    <div>{{ data }}</div>
    <son @share="(data) => (this.data = data)" :share="data" />
  </div>
</template>
<script>
import son from "./Son.vue";
export default {
  data() {
    return {
      data: "father_share_data",
    };
  },
  components: {
    son,
  },
};
</script>

 【子组件.vue 代码】

<template>
  <div id="son">
    <span>[son]</span>
    <div @click="$emit('share', data)">{{ share }}</div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      data: "son_share_data",
    };
  },
  props: ["share"],
};
</script>

可以使用语法糖 .sync / update:xxx 简化代码

在上面的例子中,父组件向子组件传了share的参数,而子组件通过click触发自定义事件要求父组件修改share参数的值,那么只用写一次share即可;而子组件$emit触发的自定义事件需要改写为'update:[prop]',意为“更新prop属性的自定义事件”(为什么特地指出?因为$emit方法就是用来触发自定义事件的,这个自定义事件的名字本来是开发者随意取的,但是使用了语法糖就限制必须要这么命名)。这样就实现父子组件双向传参。

【父组件.vue 代码】

<template>
  <div id="father">
    <div>[father]</div>
    <div>{{ data }}</div>
    <!-- 之前的 -->
    <!-- <son @share="(data) => (this.data = data)" :share="data" /> -->

    <!-- 使用sync语法糖 -->
    <son :share.sync="data" />
  </div>
</template>

【子组件.vue 代码】

<template>
  <div id="son">
    <span>[son]</span>
    <!-- 之前的 -->
    <!-- <div @click="$emit('share', data)">{{ share }}</div> -->

    <!-- 使用sync语法糖 -->
    <div @click="$emit('update:share', data)">{{ share }}</div>
  </div>
</template>

还可以使用 v-model / value / input 语法糖

这种方法限制就比较明显。首先父组件只绑定需要向子组件传参的属性,上面的例子中父组件需要向子组件传data : "father_share_data" ,则只绑定这一个参数;子组件接收的参数不能随意命名,必须命名为‘value’。而触发的自定义事件必须命名为'input'。

【父组件.vue 代码】

<template>
  <div id="father">
    <div>[father]</div>
    <div>{{ data }}</div>
    <!-- 之前的 -->
    <!-- <son @share="(data) => (this.data = data)" :share="data" /> -->

    <!-- 使用sync语法糖 -->
    <!-- <son :share.sync="data" /> -->

    <!-- 使用v-model语法糖 -->
    <son v-model="data" />
  </div>
</template>

【子组件.vue 代码】

<template>
  <div id="son">
    <span>[son]</span>
    <!-- 之前的 -->
    <!-- <div @click="$emit('share', data)">{{ share }}</div> -->

    <!-- 使用sync语法糖 -->
    <!-- <div @click="$emit('update:share', data)">{{ share }}</div> -->

    <!-- 使用v-model语法糖 -->
    <div @click="$emit('input', data)">{{ value }}</div>
  </div>
</template>
<script>
export default {
  data() {
    return {
      data: "son_share_data",
    };
  },
  props: ["value"],
};
</script>

如果开发者就是想使用v-model指令,但不想用‘value’作为属性名,也不想强制使用'input'作为自定义事件的名字。

Vue提供了一个额外的属性model,在子组件里定义这个属性,这个属性值为对象,接收两个属性 prop / event ,这两个值为String类型,即自己定义名字。修改后props里接收的属性要改成自己设定的自定义名称,$emit触发自定义事件改为自己设定的名字。父组件不用改,这样和上面的效果是一样的。

【子组件.vue 代码】

<template>
  <div id="son">
    <span>[son]</span>
    <!-- 之前的 -->
    <!-- <div @click="$emit('share', data)">{{ share }}</div> -->

    <!-- 使用sync语法糖 -->
    <!-- <div @click="$emit('update:share', data)">{{ share }}</div> -->

    <!-- 使用v-model语法糖 -->
    <!-- <div @click="$emit('input', data)">{{ value }}</div> -->

    <!-- 使用v-model语法糖,但修改成自定义prop和自定义事件名 -->
    <div @click="$emit('my_event', data)">{{ my_value }}</div>
  </div>
</template>
<script>
export default {
  data() {
    return {
      data: "son_share_data",
    };
  },
  props: ["my_value"],
  model: {
    prop: "my_value",
    event: "my_event",
  },
};
</script>

$parent / $children 父组件 ⇋ 子组件 传参

描述:通过$parent、$children 组件间可以直接访问到父子组件的实例对象。需要注意的是,页面组件的渲染顺序是父组件→子组件,父组件在创建时拿不到子组件的数据,可以用mounted钩子函数在dom挂载后,父组件调用 this.$children[xxx] 拿到子组件实例上的任何数据;而子组件创建时能拿到父组件的数据,可以直接用插入符号{{ this.$parent[xxx] }}直接拿到父组件的所有数据,甚至直接修改父组件的数据。虽然简单粗暴,但是打破单项数据流的原则,当出现复用组件且多组件依赖的情况,不慎使用会造成数据混乱。

页面展示

 【父组件.vue 代码】

<template>
  <div id="father">
    <div>[father]</div>
    <div>{{ sonData || data }}</div>
    <son />
  </div>
</template>
<script>
import son from "./Son.vue";
export default {
  data() {
    return {
      data: "father_share_data",
      sonData: null,
    };
  },
  components: {
    son,
  },
  mounted() {
    this.sonData = this.$children[0].data;
  },
};
</script>

 【子组件.vue 代码】

<template>
  <div id="son">
    <span>[son]</span>
    <div>{{ $parent.data }}</div>
  </div>
</template>
<script>
export default {
  data() {
    return {
      data: "son_share_data",
    };
  },
};
</script>

$attrs / $listeners 父组件 → 子组件 传参

$attrs 用来传属性,$listeners 用来传函数

描述:

先说 $attrs 。只要是在父组件中给子组件传参,那么这些参数都会收集到子组件实例上的 $attr 属性上,形象的说这就是一种不想在子组件中写props来接收参数的偷懒方法,对比props接收参数来说,它少了数据验证、默认值和要求必须传参等这些属于props选项所带的功能。如果开发者只想传参数不想别的,那么这招偷懒还是很好用的。

再来 $listeners 。只要是在父组件中的子组件上写的事件监听,子组件都可以通过自身的$listeners 访问到父组件的方法,并且能调用直接触发回调函数,需要注意的是触发回调函数的this指向父组件实例,相当于子组件命令父组件执行函数,并没有子组件→父组件传参。

使用 $attrs 、 $listeners 的好处是可以复用地向下级组件传参,用法为在要传参的子组件上写 v-bind='$attrs' / v-on='$listeners' ,就可以实现“较远距离”传参。 

页面展示

 

通过点击 grandson 组件的div,改变了 father 组件的data值,son 和 grandson 都使用 father 传的 :data = 'data' 值,因而都发生了改变。

【综合代码如下】

```父组件.vue
<template>
  <div id="father">
    <div>[father]</div>
    <div>{{ data }}</div>
    <son
      :data="data"
      @click="
        () => {
          data = this.handle;
        }
      "
    />
  </div>
</template>
<script>
import son from "./Son.vue";
export default {
  data() {
    return {
      data: "father_share_data",
      handle: "handleClick",
    };
  },
  components: {
    son,
  },
};
</script>
```

```子组件.vue
<template>
  <div id="son">
    <span>[son]</span>
    <div>{{ $attrs.data }}</div>
    <grandson v-bind="$attrs" v-on="$listeners" />
  </div>
</template>
<script>
import grandson from "./Grandson.vue";
export default {
  components: {
    grandson,
  },
};
</script>
```

```孙组件.vue
<template>
  <div id="grandson">
    <span>[grandson]</span>
    <div v-on="$listeners">{{ $attrs.data }}</div>
  </div>
</template>
```

 $ref 传参(父组件可以在子组件渲染完成后拿到子组件参数)

描述:ref 本义为 reference (引用)的缩写,本来作用就是创建一个引用的,比如在vm实例中获取dom元素是比较麻烦的,要不监听事件在回调函数中拿到event,再event.target拿到dom元素,或者用Vue.directive定义一个自定义事件,回调函数中第一个参数接收一个el参数(也就是绑定dom元素)可以获取到。总之比较麻烦,如果使用 ref 属性就很简单,只要在想要获取dom元素的标签上写上 ref=[自定义名称] ,通过访问该vm实例上的 $refs.xxx (自定义名称),就能拿到dom元素。

页面展示
<template>
  <input type="text" value="123" ref="input" />
</template>
<script>
export default {
  mounted() {
    console.log(this.$refs.input.value);  //123
  },
};
</script>

同理可以使用在组件上,通过此法可以拿到子组件上的数据,本质还是先拿到子组件的vm实例对象,然后再访问实例对象上的属性拿到数据。

```父组件.vue
<template>
  <div id="father">
    <div>[father]</div>
    <son ref="son" />
  </div>
</template>
<script>
import son from "./Son.vue";
export default {
  data() {
    return {
      data: "father_share_data",
    };
  },
  components: {
    son,
  },
  mounted() {
    console.log(this.$refs.son.data); //"son_share_data"
  },
};
</script>
```

```子组件.vue
<template>
  <div id="son">
    <span>[son]</span>
    <div>{{ data }}</div>
  </div>
</template>
<script>
export default {
  data() {
    return {
      data: "son_share_data",
    };
  },
};
</script>
```

$root 传参 (根组件→子孙组件传参)

描述:即 main.js 中的new Vue(options) 根实例上的data上存储参数,此后每个vm实例对象通过自身的 $root 属性拿到根组件上的data属性。需要注意的是:其他参数都是英文单词复数,$root就是英文单数。

页面展示

 

```main.js
import Vue from 'vue'
import App from './App.vue'

new Vue({
  render: h => h(App),
  data: {
    data: "root_share_data",
  },
}).$mount('#app')

```

```father.vue
<template>
  <div id="father">
    <div>[father]</div>
    <div>{{ $root.data }}</div>
  </div>
</template>
```

 $bus.$on / $emit 传参 (EventBus各组件相互通信)

描述:

①首先创建事件总线对象 $bus 。在 main.js 中给Vue构造函数原型上创一个一个名为 $bus 的Vue实例对象 Vue.prototype.$bus = new Vue();

②定义一个自定义事件。在任意一个组件实例里,使用 this.$bus.$on( 'name' , callback_fn) 指令,第一个参数接收一个String类型的自定义名字,第二个参数定义一个回调函数;

③触发自定义事件。在任意一个组件实例里,使用 this.$bus.$emit( 'name' , arg1 , arg2 ...) 指令,第一个参数接收一个String类型的需要触发的自定义名字,之后参数为给回调函数传的实参。

※需要注意的是:定义一个自定义事件一定要先于触发自定义事件。比如在父组件中 mounted钩子函数里定义一个自定义事件 'my' ,在子组件中 mounted 触发自定义事件 'my' 。会遇到触发不好使的情况。因为渲染vm的顺序是 ...父组件created → ...子组件created → ... 子组件mounted ... 父组件mounted 。看似写在父组件定义自定义事件在前,实际按渲染顺序以后把“先定义再触发的顺序搞反了”。这是可以写异步函数坚决,即调用组件实例上的 this.$nextTick(callback_fn) 方法实现异步执行。

```父组件.vue
<template>
  <div id="father">
    <div>[father]</div>
    <son />
  </div>
</template>
<script>
import son from "./Son.vue";
export default {
  components: {
    son,
  },
  mounted() {
    //定义一个自定义事件
    this.$bus.$on("my", (data) => {
      console.log(data);
    });
  },
};
</script>
```

```子组件.vue
<template>
  <div id="son">
    <span>[son]</span>
  </div>
</template>
<script>
export default {
  data() {
    return {
      data: "son_share_data",
    };
  },
  created() {
    //触发自定义事件
    this.$nextTick(() => {
      this.$bus.$emit("my",this.data); //"son_share_data"
    });
  },
};
</script>
```

 Provide & Inject 传参 (祖先模块→子孙模块)

描述:在祖先组件里使用 provide 指令,写成函数的形式,要求返回一个对象,key作为传参的名字,value作为传参的值;在子孙模块中使用 inject 指令,值为数组,数组的成员为一个个String类型的属性名。

页面展示
```father.vue
<template>
  <div id="father">
    <div>[father]</div>
    <son />
  </div>
</template>
<script>
import son from "./Son.vue";
export default {
  components: {
    son,
  },
  //使用provide指令传参
  provide() {
    return {
      father: "father.vue",
    };
  },
};
</script>
```
```son.vue
<template>
  <div id="son">
    <span>[son]</span>
    <grandson />
  </div>
</template>
<script>
import grandson from "./Grandson.vue";
export default {
  //使用provide指令传参
  provide() {
    return {
      son: "son.vue",
    };
  },
  components: {
    grandson,
  },
};
</script>
```
```grandson.vue
<template>
  <div id="grandson">
    <span>[grandson]</span>
    <div>{{ father + " " + son }}</div>
  </div>
</template>
<script>
export default {
  //使用inject指令接收参数
  inject: ["father", "son"],
};
</script>
```

 类比来说 provide/inject 和 $attrs 和 props 传参的形式类似,本质都是父组件→子孙组件。但是优劣势各有不同,以下是个人总结:

1、provide/inject:适用于跨至少一个关系的祖先元素传参给子孙元素(father和grandson的关系)。代码复杂度高,而且全写在script标签里,不易一眼就看出参数的指向。但对远距离传参不要太好用,传就provide,收就inject,很容易管理。

2、$attrs:适用于父组件给子组件传参,仅在乎把值传过去就行。代码复杂度低,只要在父组件中子组件标签的行间写上传参内容,子组件实例对象就能通过this.$attrs直接拿到值,比较方便。

3、props: 适用于父子组件传参,有参数类型、是否必须传值、默认值设置、自定义验证规则等要求的,以及需要使用双向数据绑定在父子组件通信的情况。代码复杂度适中,传参写在组件标签行间,接收使用 props 指令,是最常用的组件通信传参方式了。

暂时写这么多... 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值