父子孙组件之间的组件通信
- 面试题:vue组件之间有哪些通信方式呢?
第一种:props+this.$emit:
props:
父组件A通过v-bind绑定数据,传递数据给子组件,子组件用props接收
父组件A:
<template>
<div class="parent-root">
<child class="child" :message="message"> </child>
</div>
</template>
<script>
import child from "@/components/child";
export default {
name: "parent",
components: { child },
data() {
return {
message:"霸霸的数据"
};
},
</script>
子组件B:
<template>
<div class="child-root">
父组件传来的消息:{{message}}
</div>
</template>
<script>
export default {
name: 'child',
props: ['message'],
</script>
this.$emit:
父组件可以在使用子组件的地方直接用 v-on来监听子组件触发的事件,使用 v-on绑定自定义事件eventName,然后在子组件中使用 $emit(eventName,data)触发事件
父组件A:
子组件B:
第二种:style、class和自定义属性
父组件可以在子组件标签上添加style、class和自定义属性,子组件会合并到自己的根标签身上
父组件A:
<template>
<div class="parent-root">
<child class="child" style="color:#ccc" datd-a="5" :message="message"> </child>
</div>
</template>
<script>
import child from "@/components/child";
export default {
name: "parent",
components: { child },
data() {
return {
message:"霸霸的数据"
};
},
</script>
子组件B:
<template>
<div class="child-root">
父组件传来的消息:{{message}}
</div>
</template>
<script>
export default {
name: 'child',
props: ['message'],
</script>
第三种:this.$attrs、this.$listeners和inheritAttrs
- 父子组件间的
this.$attrs
存放了父组件传过来的除style、class、自定义属性和props接收的属性之外的其他属性,通常配合 interitAttrs 选项一起使用 - 需要注意的是:子组件里使用props接收父组件传来的属性数据之后,该属性就不在
this.$attrs
里面存放了 - 父孙之间可以使用
this.$attrs
传递数据,就不需要在每一层的子组件里都使用v-bind和props层层传递了 this.$listeners
存放了父组件用v-on(不含 .native 修饰器)的绑定的所有事件,子组件可通过v-bind:$listeners
将所有事件传递给后面的孙组件inheritAttrs
:默认为true, 会自动在挂载组件元素上属性值,如props声明了属性,则挂载未被声明的属性;false时会关闭自动挂载到组件根元素上属性。注意:这个选项不影响 class 和 style 绑定。
父组件:
<template>
<div class="parent-root">
<child class="child" :name="name" :age="age" :data="data" @change="change" @childEvent="childEvent"> </child>
<p>孙组件传出来的值:{{text}}</p>
</div>
</template>
<script>
import child from "@/components/child";
export default {
name: 'parent',
components: {child},
data() {
return {
name:"小明",
age:18,
text:'',
data:"me"
};
},
methods: {
//孙组件传出来的事件
change(text) {
this.text = text;
},
childEvent(name){
console.log("name:",name);
}
},
};
</script>
子组件:
孙组件:
-
inheritAttrs:
inheritAttrs
为true时:
inheritAttrs
为false时:
第四种: this.$parent和 this.$children
在组件内部,可以通过$parent
和$children
属性,分别得到当前组件的父组件和子组件实例
结果:
第五种:Provide
和Inject
父组件实例可以使用Provide
向后代组件传递数据,后代组件使用inject
来接收数据,
但是需要注意的是,使用这个方法传递的数据不是响应式的,然而,如果你传入了一个可监听的对象,那么其对象的 property 还是可响应的。
详细看:https://cn.vuejs.org/v2/api/?#provide-inject
父组件:
子组件:
第六种:v-model
v-model 专门用来做表单元素的当绑定在表单元素上时
- 当表单为输入表单,v-model最后会生成一个value属性和input事件
- 当绑定的是一个多选或者单选框,v-model最后生成一个checked属性和change事件
v-model
指令实质是一个语法糖,它是value
属性和input
事件的结合体
<input :value="data" @input="data=$event.target.value" />
<!-- 等同于 -->
<input v-model="data" />
详见:表单输入绑定
$event.target.value
获取触发事件的target,就是触发事件的DOM元素的value
**当v-model绑定在组件上时 ( 等同于prop+this.$emit ) **
- 父组件通过v-model传递值给子组件时,会自动传递一个value的prop属性,在子组件中通过this.$emit(‘input’,val)自动修改v-model绑定的值
<!-- 父组件 -->
<template>
<Child v-model="value" />
</template>
等同于:
<template>
<Child :value="value" @ />
</template>
<!-- 子组件 -->
<template>
<input v-model="input" />
</template>
<script>
export default {
props: {
value: String,
},
model: {
prop: 'value', // 指定 v-model 要绑定的参数叫什么名字,来自于 props 中定义的参数
event: 'change', // 指定要触发的事件名字,将被用于 $emit
},
computed: {
input: {
// 这里的计算属性使用了 getter、setter,可以简化代码
// 可参见链接 https://cn.vuejs.org/v2/guide/computed.html#%E8%AE%A1%E7%AE%97%E5%B1%9E%E6%80%A7%E7%9A%84-setter
get() {
return this.value;
},
set(val) {
this.$emit('change', val); // 触发
}
}
}
}
</script>
第七种:.sync修饰符
和v-model
的作用类似,用于双向绑定,不同点在于v-model
只能针对一个数据进行双向绑定 ,只针对input事件的value值进行绑定,而sync
修饰符没有限制
示例
<!-- 父组件 -->
<template>
<div id="app">
<Numbers :num1.sync="n1" :num2.sync="n2" />
<!-- 等同于 -->
<Numbers
:num1="n1"
@update:num1="n1 = $event"
:num2="n2"
@update:num2="n2 = $event"
/>
</div>
</template>
<script>
import Numbers from "./components/Numbers.vue";
export default {
components: {
Numbers,
},
data() {
return {
n1: 0,
n2: 0,
};
},
};
</script>
第八种:$ref
父组件可以通过ref
获取到子组件的实例
- 不过这种做法一般是不得已的时候才会使用 r e f , 因 为 ref,因为 ref,因为ref会动用真实的DOM操作,与vue的减少对DOM元素的手动操作的理念有所违背
不局限于父子孙层级关系的跨组件通信:
(兄弟与兄弟)
第一种:vuex
适用于大型项目的数据仓库
第二种:store
模式
适用于中小型项目的数据仓库
// store.js
const store = {
loginUser: ...,
setting: ...
}
// compA
const compA = {
data(){
return {
loginUser: store.loginUser
}
}
}
// compB
const compB = {
data(){
return {
setting: store.setting,
loginUser: store.loginUser
}
}
}
第三种:eventbus
组件通知事件总线发生了某件事,事件总线通知其他监听该事件的所有组件运行某个函数 (发布订阅模式)【vue3.0好像不支持事件总线的方式了?】
// 发送消息
EventBus.$emit(channel: string, callback(payload1,…))
// 监听接收消息
EventBus.$on(channel: string, callback(payload1,…))
它的工作原理是发布/订阅方法,通常称为 Pub/Sub
。
var EventBus = new Vue();
Object.defineProperties(Vue.prototype, {
$bus: {
get: function () {
return EventBus
}
}
})
在这个特定的总线中使用两个方法 $on
和 $emit
。一个用于创建发出的事件,它就是 $emit
;另一个用于订阅 $on
:
var EventBus = new Vue();
this.$bus.$emit('nameOfEvent', { ... pass some event data ...});
this.$bus.$on('nameOfEvent',($event) => {
// ...
})
然后我们可以在某个Vue页面使用 this.$bus.$emit("sendMsg", '我是web秀');
,另一个Vue页面使用
this.$bus.$on('updateMessage', function(value) {
console.log(value); // 我是web秀
})
同时也可以使用this.$bus.$off('sendMsg')
来移除事件监听。
第四种:router
如果一个组件改变了地址栏,所有监听地址栏的组件都会做出相应反应
最常见的场景就是通过点击router-link
组件改变了地址,router-view
组件就渲染其他内容
笔记相关的代码:
父组件:
<template>
<div class="parent-root">
<child
class="child"
:name="name"
:age="age"
:data="data"
@change="change"
@childEvent="childEvent"
>
</child>
<p>孙组件传出来的值:{{ text }}</p>
</div>
</template>
<script>
import child from "@/components/child";
export default {
name: "parent",
components: { child},
provide: {
foo: "bar",
},
data() {
return {
name: "小明",
age: 18,
text: "",
data: "me",
};
},
computed: {},
created() {},
methods: {
//孙组件传出来的事件
change(text) {
this.text = text;
},
childEvent(name) {
console.log("name:", name);
},
},
};
</script>
<style scoped lang="less">
.parent-root {
padding: 20px;
font-size: 16px;
position: absolute;
p {
margin-bottom: 10px;
}
}
</style>
子组件:
<template>
<div class="child-root">
<grandchild v-bind="$attrs" v-on="$listeners"> </grandchild>
<br />
<button @click="childEvent">子组件的childEvent</button>
<br />
子组件用props接收了data的数据: {{ data }}
</div>
</template>
<script>
import grandchild from "@/components/grandchild";
export default {
name: "child",
components: { grandchild },
inject: ["foo"],
props: ["data"],
//inheritAttrs:
//默认为true, 会自动在挂载组件元素上属性值,如props声明了属性,则挂载未被声明的属性
//为false时会关闭自动挂载到组件根元素上属性
inheritAttrs: false,
data() {
return {};
},
computed: {},
created() {
console.log(this.foo);
console.log("$attrs:", this.$attrs, "$listeners:", this.$listeners); //$attrs: { "name": "小明", "age": 18 },这里不会输出"class":"child"
},
mounted() {
console.log("parent:", this.$parent, "children:", this.$children);
},
methods: {
childEvent() {
console.log("child的data:", this.data);
},
},
};
</script>
<style scoped lang="less">
.child-root {
border: 1px solid #ccc;
}
</style>
孙组件:
<template>
<div class="grandchild-root">
<p>name:{{name}}</p>
<p>age:{{age}}</p>
<p>data:{{data}}</p>
<button @click="bindChange">孙子点击传给爷爷</button>
</div>
</template>
<script>
export default {
name: 'grandchild',
components: {},
props: {
name: {
type: String,
default: ''
},
age:{
type: Number,
default: null
},
data:{
type: String,
default: null
}
},
methods: {
bindChange() {
let text = '爷爷,您好'
this.$emit('change',text);
}
},
};
</script>
整理笔记看的相关文章:
vue的通信:
https://blog.csdn.net/zhoulu001/article/details/79548350
https://blog.csdn.net/Dobility/article/details/110147985
https://blog.csdn.net/weixin_42211816/article/details/116529864
v-model原理:
https://blog.csdn.net/yun_hou/article/details/86313212
https://blog.csdn.net/weixin_42380658/article/details/86568666
修饰符:
https://blog.csdn.net/qq_29468573/article/details/80771625
事件总线: