Vue中组件通讯的方式有很多种,每一种应用的场景可能都有一些不一样,我们需要在合适的场景下选择合适的通讯方式。
父子组件间通讯:props和emit、emit、emit、parent、refs和refs和refs和children、v-model
兄弟组件间通讯:事件总线、Vuex、localStorage
隔代组件间通讯:provide和inject
无相关组件间通讯:事件总线、Vuex、localStorage
props 和$emit
父组件传值给子组件
// src/views/parent.vue
<template>
<div class="parent-box">
<p>父级组件</p>
<div>
<button @click="changeMsg">更改数据</button>
</div>
<child1 :msg="msg"></child1>
<child2 :msg="msg"></child2>
</div>
</template>
<script>
import child1 from "./child1.vue";
import child2 from "./child2.vue";
export default {
data() {
return {
msg: "我是父组件的数据",
};
},
components: {
child1,
child2,
},
methods: {
// 点击按钮更改数据
changeMsg() {
this.msg = "变成小猪课堂";
},
},
};
</script>
我们将父组件中的msg通过:msg="msg"的方式传递给子组件,并且点击按钮的时候会修改父组件中的msg。
// src/views/child1.vue
<template>
<div class="child-1">
<p>child1组件</p>
<div>
<p>parent组件数据:{{ msg }}</p>
</div>
</div>
</template>
<script>
export default {
props: {
msg: {
type: String,
default: "",
},
},
};
</script>
子组件通过props属性的方式接收父组件传来的数据。
当我们点击按钮的时候,父组件的数据发生变化,子组件接收的数据也跟着发生了变化。
注意::msg="msg"接收的msg是一个变量,可以参考bind的使用原理,不加:则接收的就是一个字符串。
子组件传值给父组件
子组件可以通过$emit自定义事件的方式向父组件传递值,父组件需要监听该事件来进行接收子组件传来的值。
父组件示例代码:
// src/views/parent.vue
<template>
<div class="parent-box">
<p>父级组件</p>
<div>
<button @click="changeMsg">更改数据</button>
</div>
<div>子组件数据:{{ childData }}</div>
<child1 :msg="msg" @childData="childData"></child1>
<child2 :msg="msg"></child2>
</div>
</template>
<script>
import child1 from "./child1.vue";
import child2 from "./child2.vue";
export default {
data() {
return {
msg: "我是父组件的数据",
childData: "",
};
},
components: {
child1,
child2,
},
methods: {
changeMsg() {
this.msg = "变成小猪课堂";
},
// 监听子组件事件
childData(data) {
this.childData = data;
},
},
};
</script>
子组件示例代码:
// src/views/child1.vue
<template>
<div class="child-1">
<p>child1组件</p>
<div>
<button @click="sendData">传递数据给父组件</button>
</div>
<div>
<p>parent组件数据:{{ msg }}</p>
</div>
</div>
</template>
<script>
export default {
props: {
msg: {
type: String,
default: "",
},
},
methods: {
// 点击按钮,使用$emit向父组件传递数据
sendData() {
this.$emit("childData", "我是子组件数据");
},
},
};
</script>
我们在父组件中通过@childData="getChildData"的方式来监听childData事件,从而获取子组件传递的数据,子组件中通过点击按钮触发$emit事件向父组件传递数据。当我们点击按钮“传递数据给父组件”时,父组件便可以获取到数据。
$parent获取父组件值
这种方式可以让子组件非常方便的获取父组件的值,不仅仅包括数据,还可以是方法。
子组件示例代码:
// src/views/child1.vue
<template>
<div class="child-1">
<p>child1组件</p>
<div>
<button @click="sendData">传递数据给父组件</button>
</div>
<div>
<button @click="getParentData">使用$parent</button>
</div>
<div>
<p>parent组件数据:{{ msg }}</p>
</div>
</div>
</template>
<script>
export default {
props: {
msg: {
type: String,
default: "",
},
},
methods: {
sendData() {
this.$emit("childData", "我是子组件数据");
},
// 通过$parent方式获取父组件值
getParentData() {
console.log("父组件", this.$parent);
},
},
};
</script>
点击“使用parent”按钮时,通过parent”按钮时,通过parent获取父组件的属性或数据。
我们可以看到控制台打印出了父组件的所有属性,不仅仅包含了data数据,还有里面定义的一些方法等。
refs获取子组件值
这两种方式和$parent非常的类似,它们可以直接获取子组件的相关属性或方法,不仅限于数据。
父组件示例代码:
// src/views/parent.vue
<template>
<div class="parent-box">
<p>父级组件</p>
<div>
<button @click="changeMsg">更改数据</button>
</div>
<div>
<button @click="getChildByRef">使用$children和$refs</button>
</div>
<div>子组件数据:{{ childData }}</div>
<child1 ref="child1" :msg="msg" @childData="getChildData"></child1>
<child2 :msg="msg"></child2>
</div>
</template>
<script>
import child1 from "./child1.vue";
import child2 from "./child2.vue";
export default {
data() {
return {
msg: "我是父组件的数据",
childData: "",
};
},
components: {
child1,
child2,
},
methods: {
changeMsg() {
this.msg = "变成小猪课堂";
},
// 监听子组件的自定义事件
getChildData(data) {
this.childData = data;
},
// 使用$chilren和$refs获取子组件
getChildByRef() {
console.log("使用$children", this.$children);
console.log("使用$refs", this.$refs.child1);
},
},
};
</script>
上段代码中,我们点击按钮,分别通过children和children和children和refs的方式获取到了子组件,从而拿到子组件数据。需要注意的是,children会返回当前组件所包含的所有子组件数组,使用children会返回当前组件所包含的所有子组件数组,使用children会返回当前组件所包含的所有子组件数组,使用refs时,需要在子组件上添加ref属性,有点类似于直接获取DOM节点的操作。
使用listeners
arrts的方式,毕竟Vuex还是比较重。
当父组件传递了很多数据给子组件时,子组件没有声明props来进行接收,那么子组件中的attrs属性就包含了所有父组件传来的数据(除开已经props声明了的),子组件还可以使用v−bind="attrs属性就包含了所有父组件传来的数据(除开已经props声明了的),子组件还可以使用v-bind="attrs属性就包含了所有父组件传来的数据(除开已经props声明了的),子组件还可以使用v−bind="attrs"的形式向它的子组件(孙子组件)传递数据,孙子组件使用$attrs的方式和它的父组件原理类似。
$attrs的使用
我们在parent父组件中多传一点数据给child1组件。
parent组件示例代码:
// src/views/parent.vue
<template>
<div class="parent-box">
<p>父级组件</p>
<child1
ref="child1"
:msg="msg"
:msg1="msg1"
:msg2="msg2"
:msg3="msg3"
:msg4="msg4"
@childData="getChildData"
></child1>
</div>
</template>
<script>
import child1 from "./child1.vue";
import child2 from "./child2.vue";
export default {
data() {
return {
msg: "我是父组件的数据",
msg1: "parent数据1",
msg2: "parent数据2",
msg3: "parent数据3",
msg4: "parent数据4",
childData: "",
};
},
components: {
child1,
child2,
}
};
</script>
这里我们删除了一些本节用不到的代码,大家需要注意一下。
child1组件示例代码:
// src/views/child1.vue
<template>
<div class="child-1">
<p>child1组件</p>
<!-- 子组件child1-child -->
<child1-child v-bind="$attrs"></child1-child>
</div>
</template>
<script>
import Child1Child from "./child1-child";
export default {
components: {
Child1Child,
},
props: {
msg: {
type: String,
default: "",
},
},
mounted() {
console.log("child1组件获取$attrs", this.$attrs);
}
};
</script>
上段代码中我们的parent父组件传递了5个数据给子组件:msg、msg1、msg2、msg3、msg4。但是在子组件中的props属性里面,我们只接收了msg。然后我们在子组件mounted中打印了$attrs,发现恰好少了props接收过的msg数据。
当我们在child1组件中使用attrs接收了组件后,可以使用v−bind="attrs接收了组件后,可以使用v-bind="attrs接收了组件后,可以使用v−bind="attrs"的形式在传递给它的子组件child1-child,上段代码中我们已经加上了v-bind。
child1-child组件示例代码:
// src/views/child1-child.vue
<template>
<div class="child1-child">
<p>我是孙子组件child1-child</p>
</div>
</template>
<script>
export default {
props: {
msg1: {
type: String,
default: "",
},
},
mounted() {
console.log("child1-child组件$attrs", this.$attrs);
},
};
</script>
我们发现child1-child组件中打印的$attrs中少了msg1,因为我们已经在props中接收了msg1。
$listeners 的使用
listeners属性和attrs属性和类型,只是它们传递的东西不一样。
当父组件在子组件上定义了一些自定义的非原生事件时,在子组件内部可以通过listeners用来传递非原生事件,我们在child1组件中打印一下看看。
child1组件示例代码:
// src/views/child1.vue
mounted() {
console.log("child1组件获取$attrs", this.$attrs);
console.log("child1组件获取$listeners", this.$listeners);
},
可以发现输出了childData方法,这是我们在它的父组件自定义的监听事件。除次之外,$listeners可以通过v-on的形式再次传递给下层组件。
child1组件示例代码:
// src/views/child1.vue
<template>
<div class="child-1">
<p>child1组件</p>
<div>
<button @click="sendData">传递数据给父组件</button>
</div>
<div>
<button @click="getParentData">使用$parent</button>
</div>
<div>
<p>parent组件数据:{{ msg }}</p>
</div>
<!-- 子组件child1-child -->
<child1-child v-bind="$attrs" v-on="$listeners"></child1-child>
</div>
</template>
child1-child组件示例代码:
// src/views/child1-child.vue
mounted() {
console.log("child1-child组件$attrs", this.$attrs);
console.log("child1-child组件$listerners", this.$listeners);
},
可以看到在child1-child孙子组件中也获得了parent父组件中的childData自定义事件。使用listeners的好处在于:如果存在多层级组件,无需使用listeners的好处在于:如果存在多层级组件,无需使用listeners的好处在于:如果存在多层级组件,无需使用emit的方式逐级向上触发事件,只需要使用$listerners就可以得到父组件中的自定义事件,相当于偷懒了。
inheritAttrs
父组件传递了很多数据给子组件,子组件的props没有完全接收,那么父组件传递的这些数据就会渲染到HTML上,我们可以给子组件设置inheritAttrs 为false,避免这样渲染。
child1组件示例代码:
// src/views/child1.vue
props: {
msg: {
type: String,
default: "",
},
},
inheritAttrs: false,
总结
listeners:用来传递事件,出了原生事件,它也是一个对象。
attrs和attrs和attrs和listeners这两个属性可以解决多层组件之间数据和事件传递的问题。
inheritAttrs解决未使用props接收的数据的属性渲染。
自定义事件:事件总线
在我们做项目的时候,会发现不相关的组件之间的数据传递是较为麻烦的,比如兄弟组件、跨级组件,在不使用Vuex情况下,我们可以使用自定义事件(也可以称作事件中心)的方式来实现数据传递。
事件中心的思想也比较简单:中间中心主要就两个作用:触发事件和监听事件。假如两个组件之间需要传递数据,组件A可以触发事件中心的事件,组件B监听事件中心的事件,从而让两个组件之间产生关联,实现数据传递。
实现步骤:
为了演示简单,我们在全局注册一个事件中心,修改main.js。
main.js代码如下:
// src/main.js
Vue.config.productionTip = false
Vue.prototype.$EventBus = new Vue()
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
child1组件示例代码:
<template>
<div class="child-1">
<p>child1组件</p>
<div>
<button @click="toChild2">向child2组件发送数据</button>
</div>
</div>
</template>
<script>
import Child1Child from "./child1-child";
export default {
methods: {
// 通过事件总线向child2组件发送数据
toChild2() {
this.$EventBus.$emit("sendMsg", "我是child1组件发来的数据");
},
},
};
</script>
child1组件中调用EventBus.EventBus.emit向事件中心添加sendMsg事件,这个用法有点类似与props和$emit的关系。
child2组件2示例代码:
// src/views/child1.vue
<template>
<div class="child-2">
<p>child2组件</p>
<div>
<p>parent组件数据:{{ msg }}</p>
</div>
</div>
</template>
<script>
export default {
props: {
msg: {
type: String,
default: "",
},
},
mounted() {
this.$EventBus.$on("sendMsg", (msg) => {
console.log("接收到child1发送来的数据", msg);
});
},
};
</script>
当我们点击child1组件中的按钮时,就会触发sendMsg事件,在child2组件中我们监听了该事件,所以会接收到child1组件发来的数据。
事件中心实现数据传递的这种方式,其实就是一个发布者和订阅者的模式,这种方式可以实现任何组件之间的通信。
provide和inject
这两个是在Vue2.2.0新增的API,provide和inject需要在一起使用。它们也可以实现组件之间的数据通信,但是需要确保组件之间是父子关系。
简单一句话:父组件可以向子组件(无论层级)注入依赖,每个子组件都可以获得这个依赖,无论层级。
parent示例代码:
// src/views/parent.vue
<script>
import child1 from "./child1.vue";
import child2 from "./child2.vue";
export default {
provide() {
return { parentData: this.msg };
},
data() {
return {
msg: "我是父组件的数据",
msg1: "parent数据1",
msg2: "parent数据2",
msg3: "parent数据3",
msg4: "parent数据4",
childData: "",
};
},
components: {
child1,
child2,
},
};
</script>
child1-child组件示例代码:
// src/views/child1-child.vue
<template>
<div class="child1-child">
<p>我是孙子组件child1-child</p>
<p>parent组件数据:{{parentData}}</p>
</div>
</template>
<script>
export default {
inject: ["parentData"],
props: {
msg1: {
type: String,
default: "",
},
},
mounted() {
console.log("child1-child组件$attrs", this.$attrs);
console.log("child1-child组件$listerners", this.$listeners);
console.log("child1-child组件获取parent组件数据", this.parentData)
},
};
</script>
通过provide和inject结合的方式,我们在child1-child组件中获取到了parent组件中的数据。如果你下来尝试过的话,可能会发现一个问题,此时数据不是响应式,也就是parent组件更改了数据,child1-child组件中的数据不会更新。
想要变为响应式的,我们需要修改一下provide传递的方式。
parent代码如下:
// src/views/parent.vue
<script>
import child1 from "./child1.vue";
import child2 from "./child2.vue";
export default {
provide() {
return { parentData: this.getMsg };
},
data() {
return {
msg: "我是父组件的数据",
msg1: "parent数据1",
msg2: "parent数据2",
msg3: "parent数据3",
msg4: "parent数据4",
childData: "",
};
},
components: {
child1,
child2,
},
methods: {
// 返回data数据
getMsg() {
return this.msg;
},
},
};
</script>
这个时候我们会发现数据变为响应式的了。
porvide和inject的原理可以参考下图:
Vuex和localStorage
Vuex:
Vuex是状态管理器,它存储的数据不是持久化存储,一旦刷新页面或者关闭项目数据便不见了。
Vuex存储的数据是响应式的。
localstorage:
loacalStorage是HTML5中的一种数据存储方式,持久化存储,存储的数据不是响应式的。
v-model
v-model是vue中的一个内置指令,它通常用在表单元素上以此来实现数据的双向绑定,它的本质是v-on和v-bind的语法糖。在这里我们也可以借助它来实现某些场景下的数据传递。注意,这儿的场景必须是父子组件。
parent组件示例代码:
<template>
<div class="parent-box">
<p>父级组件</p>
<div>modelData: {{modelData}}</div>
<child2 :msg="msg" v-model="modelData"></child2>
<!-- 实际等同于 -->
<!-- <child2 v-bind:value="modelData" v-on:input="modelData=$event"></child2> -->
</div>
</template>
<script>
import child2 from "./child2.vue";
export default {
provide() {
return { parentData: this.getMsg };
},
data() {
return {
modelData: "parent组件的model数据"
};
},
components: {
child1,
},
};
</script>
child2组件示例代码:
<template>
<div class="child-2">
<p>child2组件</p>
<div>
<button @click="confirm">修改v-model数据</button>
</div>
</div>
</template>
<script>
export default {
props: {
value: {
type: String,
default: "",
},
},
mounted() {
console.log("child2组件接收附件见v-model传递的数据", this.value);
},
methods: {
// 通过$emit触发父组件的input事件,并将第二个参数作为值传递给父组件
confirm() {
this.$emit("input", "修改parent传递的v-model数据");
},
},
};
</script>
我们在父组件中使用v-model向child2子组件传递数据,子组件的props中使用默认的value属性接收,在子组件中利用$emit触发父组件中默认input事件,此时传递的数据便会在子组件和父组件中发生变化,这就是数据双向绑定。
如果想要更加详细的学习v-model的使用,可以参考官网。
作者:小猪课堂
链接:https://juejin.cn/post/7086735244992200734
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。