Vue2.x 组件通信总共有一下几种,具体多少种不同的人可能分类不同。
- props :父级向子级传递数据,单向;
- $emit / v-on :子级通过触发事件向父级传递数据 ,单向;
- .sync : 父子之间通信 双向,相当于组合了props和$emit/v-on的语法糖;
- v-model : 父子之间通信 双向,可以理解为一个特殊化的 .sync ;
- ref :父级通过ref获取子级的实例,从而可以获取子级的数据或者调用方法;
- $children\ $parent:子级通过 $parent 访问父级实例从而获取属性和方法,兄弟组件也可以通过这个属性进行通信;父级可以通过 $children 获取所有的子组件(不含孙子组件)实例从而获取属性和方法;
- $attrs 和 $listeners:子级的两个属性接收父级传来的属性和监听器,可以通过这两个属性直接传递给下一级;
- provide / inject:依赖注入,父级提供数据和方法,后代无论层级多深都可以接收;
- EventBus:事件总线 父子组件,兄弟组件,跨层级组件都可以用它来通信;
- Vuex:集中管理所以组件状态;
- $root:访问根实例,通过根实例通信;
- slot:子组件的数据通过插槽传递给父组件;
Vue 中的三个缩写
v-bind (:)
v-on (@)
v-slot (#)
props
父组件向子组件传递数据,子组件使用(props)接收到数据后使用,但是不可以直接修改。因为父组件重新渲染的时候子组件会的修改被覆盖;所以只能用于父组件向子组件单向传递数据。
例:
<!--parent.vue-->
<template>
<div id="app">
<Children :message="'danger'" :message2="message2"></Children>
</div>
</template>
<script>
import Children from "./Children.vue";
export default {
name: "parent",
data() {
return {
message2: "success",
};
},
components: {
Children,
},
};
</script>
<!-- children.vue -->
<template>
<div>{{ message }} {{ message2 }}</div>
</template>
<script>
export default {
// 第一种写法
// props:["message","message2"]
// 第二种校验传入的数据,开发模式下如果传入的不符合规则,控制台会输出一个警告
props: {
message2: String,
message: {
type: String, // 类型为String,还可以填写其他类型,或者自定义类型
required: true, // 必填
default: "default data",
validator: function (value) {
return ["success", "warning", "danger"].includes(value);
},
},
},
mounted() {
console.log(this.message,this.message2);
},
};
</script>
$emit v-on
子组件触发事件,父级接收对应的数据。
// children.vue
// .... 省略其他代码
methods: {
handleMessage() {
this.$emit("setMessage", "warning");
},
},
.....
<template>
<div>
<Children @set-message="updateData"></Children>
</div>
</template>
<script>
import Children from "./Children.vue";
export default {
components: {
Children,
},
methods: {
updateData(data) {
console.log(data);
},
},
};
</script>
.sync
父子之间通信(双向绑定);其实 sync 是$emit + v-on + props组合使用的一种语法糖, $emit + v-on + props是一个常用组合,用的人太多所以就提供了一个语法糖,以减少模板代码。
先看下如果不使用sync,如何实现父子之间数据的双向绑定:
// 子组件接收数据,触发事件
export default {
props: ["message"],
methods: {
handleMessage() {
this.$emit('update:message', 'danger')
},
},
};
<!-- 父组件接收事件并更新数据-->
<text-document
v-bind:message="message"
v-on:update:message="message = $event"
></text-document>
接下来使用sync实现父子组件之间的通信,我们可以看到父组件不需要写v-on来监听事件了以减少模板代码;而且由于父组件只需要写一个属性,也防止了有人把v-bind和v-on两者写的很远,导致出现难以看懂的代码。
<template>
<!-- parent.vue -->
<div>
<Children :message2.sync="message2"></Children>
</div>
</template>
<script>
import Children from "./Children.vue";
export default {
name: "parent",
data() {
return {
message2: "success",
};
},
components: {
Children,
},
};
</script>
<template>
<!-- Children.vue -->
<div>
{{ message2 }}
</div>
</template>
<script>
export default {
props: ["message2"],
mounted() {
console.log(this.message2);
},
// 可以和计算属性结合使用是个不错的组合。
computed: {
childrenMsg: {
get() {
return this.message2;
},
set(newVal) {
this.$emit("update:message2", newVal);
},
},
},
methods: {
handleMessage() {
this.$emit("update:message2", "danger");
},
},
};
</script>
v-model
v-model和 .sync 类似,能够实现父子组件数据的双向绑定,v-model可以看做sync的一个特例,v-model相当于一个默认会使用value字段传递数据和input的事件更新数据的sync。还可以通过子组件的model
属性来指定绑定的字段和触发器。自定义组件的model
<!-- parent.vue -->
<template>
<div>
<Children v-model="message2"></Children>
</div>
</template>
<script>
import Children from "./Children.vue";
export default {
name: "parent",
data() {
return {
message2: "success",
};
},
components: {
Children,
},
};
</script>
子组件触发input事件即可同步更新数据;
<template>
<!-- Children.vue -->
<div @click="handleMessage">
{{ value }}
</div>
</template>
<script>
export default {
props: ["value"],
mounted() {
console.log(this.value);
},
// 可使用model修改默认的prop和event,这里修改了之后,props还是要添加对应的声明
// model: {
// prop: 'checked',
// event: 'change'
// },
methods: {
handleMessage() {
this.$emit("input", "danger");
},
},
};
</script>
和sync相比代码是不是又简单些呢 (*^▽^*);同时sync父子通信的时候字段名必须一致;如果父级传来的字段不符合子组件的要求,想要修改为其它名还需要使用计算属性;而v-model则由子组件决定传进来的数据字段名,也减少了计算属性这一层;缺点就是,Vue2中每个组件只支持一个v-model,Vue3 会升级这个的。
ref
ref如果在普通DOM元素上,那么指向的就是该DOM元素。如果是子组件则指向了该组件实例。父组件就可以通过ref来获取子组件的属性和方法了,从而实现父子组件之间的通信。
<template>
<!-- parent.vue -->
<div>
<Children ref="child"></Children>
</div>
</template>
<script>
import Children from "./Children.vue";
export default {
name: "parent",
data() {
return {
message2: "success",
};
},
components: {
Children,
},
methods: {
getData() {
this.$refs.child.name; // 访问属性
this.$refs.child.getChildData(); // 访问方法
},
},
};
</script>
<!-- children.vue-->
<script>
export default {
data() {
return {
name: "hello",
};
},
methods: {
getChildData(message) {
console.log(message);
},
},
};
</script>
$children $parent
$children:一个包含所有子组件(不包含孙子组件)的 VueComponent 对象数组,从而拿到子组件中所有数据和方法;
$parent:获取到一个父节点的 VueComponent 对象,从而可以获取到父节点中所有数据和方法。
节制地使用 $parent 和 $children - 它们的主要目的是作为访问组件的应急方法。更推荐用 props 和 events 实现父子组件通信
<template>
<!-- parent.vue -->
<div>
<Children></Children>
</div>
</template>
<script>
import Children from "./Children.vue";
export default {
name: "parent",
data() {
return {
message2: "success",
};
},
components: {
Children,
},
mounted() {
this.getData();
},
methods: {
getData() {
this.$children[0].getChildData("parent");
},
},
};
</script>
<script>
export default {
data() {
return {
name: "hello",
};
},
methods: {
getChildData(message) {
console.log(message);
},
getParentData() {
console.log(this.$parent.message2);
},
},
};
</script>
$attrs $listeners
$attrs 包含父组件传来的不被props识别的attribute(class和style除外),还可以通过v-bind="$attrs"传递给内部组件,当父级组件传递过来的属性,本层级并不需要处理而只是需要传递给子组件的时候,并不需要使用props接收下来再传递给子组件这种操作而是直接使用v-bind="$attrs",这样就可以把只要本层级没有使用props接收的属性都会自动传递给子组件了(class和style除外)。
默认情况下父组件的不被认作 props 的 attribute 绑定 (attribute bindings) 将会“回退”且作为普通的 HTML attribute 应用在子组件的根元素上。当撰写包裹一个目标元素或另一个组件的组件时,这可能不会总是符合预期行为。通过设置 inheritAttrs 到 false,这些默认行为将会被去掉。而通过 (同样是 2.4 新增的) 实例 property $attrs 可以让这些 attribute 生效,且可以通过 v-bind 显性的绑定到非根元素上。
<div a="1" b="2">
this is test hello danger
<div>this is from the bottom</div>
</div>
例如父组件传来的a和b子组件没有使用props接收,所以默认就当做普通属性应用到了子组件的根元素上。可以通过inheritAttrs 属性来阻止这种行为,转而使用$attrs来处理。这个被称作 “隐式贯穿行为”。事件监听器也有类似行为。
$listeners包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on="$listeners" 传入内部组件,$listeners 和$attrs的区别在于传入内部组件的事件,在本层级触发事件父组件也可以监听到也就是父组件可以同时监听到子组件和孙子组件触发的事件(Vue3 会修改这个)。
这两个属性在创建更高层次的组件时非常有用(非常好用,b( ̄▽ ̄)d)。
<template>
<!-- parent.vue -->
<div>
<Children :message="message2" title="Otis"></Children>
</div>
</template>
<script>
import Children from "./Children.vue";
export default {
name: "parent",
data() {
return {
message2: "success",
};
},
components: {
Children,
},
};
</script>
<template>
<!-- Children.vue -->
<div>
<son-child v-bind="$attrs" />
</div>
</template>
<script>
export default {
props: ["message"],
methods: {
getData() {
// 如果props接收了 message $attrs = { title:"Otis" },如果没有接手则 $attrs = { message:"success", title:"Otis" }
console.log(this.$attrs);
},
},
};
</script>
provide / inject
provide / inject 为依赖注入,文档说说是不推荐直接用于应用程序代码中,但是在一些插件或组件库里却是被常用,所以我觉得用也没啥,还挺好用的,因为起决定作用的还是人呀。
provide:可以让我们指定想要提供给后代组件的数据或方法
inject:在任何后代组件中接收想要添加在这个组件上的数据或方法,不管组件嵌套多深都可以直接拿来用
要注意的是 provide 和 inject 传递的数据不是响应式的,也就是说用 inject 接收来数据后,provide 里的数据改变了,后代组件中的数据不会改变,除非传入的就是一个可监听的对象。
所以建议还是传递一些常量或者方法
<template>
<!-- parent.vue -->
<div>
<Children :message="message2" title="Otis"></Children>
</div>
</template>
<script>
import Children from "./Children.vue";
export default {
name: "parent",
// 这种写法不能访问this
// provide: {
// foo: "bar",
// },
provide() {
return {
foo: this.message2,
hello: this.hello,
};
},
data() {
return {
message2: "success",
};
},
components: {
Children,
},
methods: {
hello() {
console.log("hello world");
},
},
};
</script>
<template>
<!-- Children.vue -->
<div>
<div v-bind="$attrs">{{ foo }}</div>
</div>
</template>
<script>
export default {
data() {
return {
fo: this.foo,
};
},
inject: ["foo", "hello"],
props: ["message"],
mounted() {
this.getData();
},
methods: {
getData() {
console.log(this.foo);
console.log(this.fo, "fo");
this.hello();
},
},
};
</script>
EventBus
事件总线,顾名思义它主要通过事件来进通信,不管是父子组件,兄弟组件,跨层级组件等都可以使用它完成通信操作。
// 方法一 单独的文件中,然后在需要的地方引入
// Bus.js
import Vue from "vue"
export default new Vue()
// 方法二 直接挂载到全局
// main.js
import Vue from "vue"
Vue.prototype.$bus = new Vue();
// 有时候为了防止被人修改,还会写成这样
const EventBus = new Vue();
Object.defineProperties(Vue.prototype, {
$bus: {
get() {
return EventBus;
},
},
});
<script>
import Bus from "./Bus.js"
export default{
methods:{
handlerClick(){
// 自定义事件名 sendMsg 触发事件
Bus.$emit("sendMsg", "message");
// 或者
this.$bus.$emit("sendMsg", "message")
}
}
}
// 接收事件
import Bus from "./Bus.js"
export default{
mounted(){
Bus.$on("sendMsg", data => {
console.log("got a message:", data)
})
this.$bus.$on("sendMsg", data => {
console.log("got a message:", data)
});
},
beforeDestroy(){
// 组件销毁的时候要取消监听事件,不然如果组件多次挂载可能会多次注册事件导致多次触发事件
Bus.$off("sendMsg");
this.$bus.$off('sendMsg');
}
}
</script>
Vuex
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态。
$root
可以访问到App.vue中的属性和方法即根实例的属性和方法。也可把 $root 当做事件总线来使用,只是说可以而已但是不要这么用。
slot
就是把子组件的数据通过插槽的方式传给父组件使用
<template>
<!-- parent.vue -->
<div>
<Children>
<template v-slot="slotData">
{{ slotData.lastName }}
</template>
<template #girl="slotData"> ruby {{ slotData.firstName }} </template>
<template v-slot:girl2="slotData">
ruby {{ slotData.firstName }}
</template>
</Children>
</div>
</template>
<script>
import Children from "./Children.vue";
export default {
name: "parent",
data() {
return {
name: "Otis",
};
},
components: {
Children,
},
};
</script>
<template>
<!-- Children.vue -->
<div>
<slot :lastName="name"></slot>
<hr />
<slot name="girl" :firstName="firstName" />
<hr />
<slot name="girl2" :firstName="foo" />
</div>
</template>
<script>
export default {
data() {
return {
name: "Otis",
firstName: "Maevel",
foo: "maevel",
};
},
};
</script>
很多事情开始的时候看起来简单,当你意识到它的复杂的时候,你已经无力回天。