1. props 和$emit
这种方式通常用于父子组件之间的传值,父组件通过自定义属性的方式将值传递给子组件,子组件通过props进行接收。
子组件通过$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>
</div>
</template>
<script>
import child1 from "./child1.vue";
export default {
data() {
return {
msg: "我是父组件的数据",
childData: "",
};
},
components: {
child1
},
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>
2. $parent和$children
$parent子组件获取父组件属性和方法
子组件
// src/views/child1.vue
<template>
<div class="child-1">
<p>child1组件</p>
<div>
<button @click="getParentData">使用$parent</button>
</div>
<div>
<p>parent组件数据:{{ msg }}</p>
</div>
</div>
</template>
<script>
export default {
props: {
msg: {
type: String,
default: "",
},
},
methods: {
// 通过$parent方式获取父组件值
getParentData() {
// 可以打印出所有父组件的属性和方法
console.log("父组件", this.$parent);
},
},
};
</script>
$children 获取子组件数据
直接获取子组件的相关属性或方法,不仅限于数据。
父组件
// src/views/parent.vue
<template>
<div class="parent-box">
<p>父级组件</p>
<div>
<button @click="changeMsg">更改数据</button>
</div>
<div>
<button @click="getChildByRef">使用$children</button>
</div>
<div>子组件数据:{{ childData }}</div>
<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: {
// 监听子组件的自定义事件
getChildData(data) {
this.childData = data;
},
// 使用$chilren取子组件
getChildByRef() {
// 得到所有子组件的所有属性和方法
console.log("使用$children", this.$children);
},
},
};
</script>
3. $attrs和$listeners
$attrs是在Vue2.4.0之后新提出的,通常在多层组件传递数据的时候使用。
$attrs的使用
$attrs是在Vue2.4.0之后新提出的,通常在多层组件传递数据的时候使用。
在父组件传递给第一级子组件,时用:自定义事件="数据"传递,第一级子组件用this.$attrs获取传递过来没用props接收的数据;若第一级子组件想向第二级组件传递数据,用在标签中添加v-bind="attrs"属性,同样第二级子组件用this.$attrs接收传递过来的数据,以此类推。
父组件
// src/views/parent.vue
<template>
<div class="parent-box">
<p>父级组件</p>
<child1
:msg="msg"
:msg1="msg1"
:msg2="msg2"
:msg3="msg3"
:msg4="msg4"
></child1>
</div>
</template>
<script>
import child1 from "./child1.vue";
export default {
data() {
return {
msg: "我是父组件的数据",
msg1: "parent数据1",
msg2: "parent数据2",
msg3: "parent数据3",
msg4: "parent数据4",
};
},
components: {
child1
}
};
</script>
第一级子组件
this.$attrs获取传递过来没用props接收的数据,并向第二级组件传递数据,在在标签中添加v-bind="attrs"属性
// 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() {
// 返回父级传递过来没用props接收的数据
console.log("child1组件获取$attrs", this.$attrs);
}
};
</script>
上段代码中我们的parent父组件传递了5个数据给子组件:msg、msg1、msg2、msg3、msg4。但是在子组件中的props属性里面,我们只接收了msg。然后我们在子组件mounted中打印了$attrs,发现恰好少了props接收过的msg数据。
第二级子组件
// 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() {
// 返回第一级子组件没用props接收的数据
console.log("child1-child组件$attrs", this.$attrs);
},
};
</script>
我们发现child1-child组件中打印的$attrs中少了msg1,因为我们已经在props中接收了msg1。
$listeners 的使用
listeners属性和attrs属性和类型,只是它们传递的东西不一样。
listeners和attrs的区别很明显,attrs的区别很明显,attrs用来传递属性,$listeners用来传递非原生事件
当父组件在子组件上定义了一些自定义的非原生事件时,在子组件内部可以通过$listeners属性获取到这些自定义事件。
第一级子组件
// 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>
<script>
mounted() {
console.log("child1组件获取$attrs", this.$attrs);
console.log("child1组件获取$listeners", this.$listeners);
}
</script>
可以发现输出了childData方法,这是我们在它的父组件自定义的监听事件。除此之外,$listeners可以通过v-on的形式再次传递给下层组件。
attrs的bug)
我们在使用$attrs时,child1子组件渲染的DOM节点上将我们传递的属性一起渲染了出来
- inheritAttrs解决未使用props接收的数据的属性渲染。
父组件传递了很多数据给子组件,子组件的props没有完全接收,那么父组件传递的这些数据就会渲染到HTML上,我们可以给子组件设置inheritAttrs 为false,避免这样渲染。
4. 自定义事件:事件总线
总线使用两个方法
$on和$emit。一个用于创建发出的事件,它就是$emit;另一个用于订阅 监听获取$on
事件总线使用时,一定要注意名字不能重名,尤其是多个组件共用的时候
这里用全局的
// 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>
export default {
methods: {
// 通过事件总线向child2组件发送数据
toChild2() {
this.$EventBus.$emit("sendMsg", "我是child1组件发来的数据");
},
},
};
</script>
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>
这里用的是局部
// src/utils/eventBus.js
improt bus from 'vue'
exprot default new Vue()
child1组件示例代码:
<template>
<div class="child-1">
<p>child1组件</p>
<div>
<button @click="toChild2">向child2组件发送数据</button>
</div>
</div>
</template>
<script>
improt bus from "@/utils/eventBus.js"
export default {
methods: {
// 通过事件总线向child2组件发送数据
toChild2() {
bus.$emit("sendMsg", "我是child1组件发来的数据");
},
},
};
</script>
child2组件2示例代码:
// src/views/child1.vue
<template>
<div class="child-2">
<p>child2组件</p>
<div>
<p>parent组件数据:{{ msg }}</p>
</div>
</div>
</template>
<script>
improt bus from "@/utils/eventBus.js"
export default {
props: {
msg: {
type: String,
default: "",
},
},
mounted() {
bus.$on("sendMsg", (msg) => {
console.log("接收到child1发送来的数据", msg);
});
},
};
</script>
5. provide和inject
Vue2.2.0新增的API,provide和inject需要在一起使用。
父组件可以向子组件(无论层级)注入依赖,每个子组件都可以获得这个依赖,无论层级。
parent(爷爷)示例代码:
// src/views/parent.vue
<script>
import child1 from "./child1.vue";
export default {
provide() {
return { parentData: this.msg };
},
data() {
return {
msg: "我是父组件的数据",
};
},
components: {
child1
},
};
</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: {},
mounted() {
console.log("child1-child组件获取parent组件数据", this.parentData)
},
};
</script>
如果想让传参,变成响应式的,就用如下方法写parent(爷爷)组件
// src/views/parent.vue
<script>
import child1 from "./child1.vue";
export default {
provide() {
return { parentData: this.getMsg };
},
data() {
return {
msg: "我是父组件的数据",
};
},
components: {
child1
},
methods: {
// 返回data数据
getMsg() {
return this.msg;
},
},
};
</script>
6. Vuex和localStorage
这种方式应该是小伙伴们在实际项目中使用最多的
Vuex:
- Vuex是状态管理器,它存储的数据不是持久化存储,一旦刷新页面或者关闭项目数据便不见了。
- Vuex存储的数据是响应式的。
localstorage:
- loacalStorage是HTML5中的一种数据存储方式,持久化存储,存储的数据不是响应式的。
总结
-
父子组件间通讯:props和emit、emit、emit、parent、refs和refs和refs和children、v-model
-
兄弟组件间通讯:事件总线、Vuex、localStorage
-
隔代组件间通讯:provide和inject
-
无相关组件间通讯:事件总线、Vuex、localStorage