这是我参与更文挑战的第19天,活动详情查看: 更文挑战
组件是 vue 的强大功能之一,而各个组件的作用域又是独立的,也就是各组件的数据是相对独立的,因此组件之间的通信就变成了核心要解决的问题,通常情况组件有一下几种情况:
如上图所示,A 和 B、B 和 C、B 和 D 都是父子关系,C 和 D 是兄弟关系,A 和 C、A 和 D 是隔代关系(可能隔多代)。
方法一:props / $emit
此方法用于父子组件的通信。父组件 A 通过 props 的方式向子组件 B 传递,在子组件 B 中通过 $emit
的方式向父组件 A 传值, A 组件中使用 v-on 接收 B 组件传过来的值。举个例子:
1 父组件向子组件传值:
父组件代码:
<template>
<div class="list">
<item :goods="goods"></item>
</div>
</template>
<script>
import Item from "./components/Item"
export default {
name: 'App',
data(){
return{
goods: {
name: 'iPhone XS Max',
price: 10000
}
}
},
components:{
"item":item
}
}
</script>
复制代码
子组件代码:
<template>
<div>
商品名称:{{goods.name}}
商品价格:{{goods.price}}
</div>
</template>
<script>
export default {
props: {
goods: {
type: Object,
default: null
}
}
}
</script>
复制代码
父组件通过props
向下传递数据给子组件。注:组件中的数据共有三种形式:data
、props
、computed
。
2 子组件向父组件传值
子组件
<template>
<div>
商品名称:{{goods.name}}
商品价格:{{goods.price}}
<el-button @click='changePrice'>点击</el-button>
</div>
</template>
<script>
export default {
props: {
goods: {
type: Object,
default: null
}
},
methods: {
changePrice () {
// 派发 change 事件供父组件监听,并传递值 “子组件向父组件的值”
this.$emit('change', '子组件向父组件传的值')
}
}
}
</script>
复制代码
父组件
<template>
<div class="hello">
<item
:goods="goods"
@change='changePrice'
></item>
</div>
</template>
<script>
import Item from '@/components/Item'
export default {
data () {
return {
goods: {
name: 'iPhone XS Max',
price: 10000
}
}
},
methods: {
// 使用 v-on(缩写成@)监听 change 事件,参数为子组件传过来的值
changePrice (val) {
console.log(val)
this.goods.price = val
}
},
components: { Item }
}
</script>
复制代码
方法二:中央事件总线 eventBus $emit / $on
这种方法通过一个空的 Vue
实例作为中央事件总线(事件中心),用它来触发事件和监听事件,巧妙而轻量地实现了任何组件间的通信,包括父子、兄弟、跨级。
1 具体实现方式
var Event=new Vue(); // 定义一个新的Vue实例
Event.$emit(事件名,数据); // 使用 $emit 来传递
Event.$on(事件名,data => {}); // 使用 $on 来接收
复制代码
2 例子说明
A、B、C是三个兄弟组件,A 和 B 要传递数据给 C,C 要从 A 和 B 中接收数据。
A.vue
<template>
<div>
<el-button @click='giveDataToC'>A传值给C</el-button>
</div>
</template>
<script>
import Vue from 'vue'
window.Event = new Vue()
export default {
methods: {
giveDataToC () {
window.Event.$emit('data-A', 'data from A')
}
}
}
</script>
复制代码
B.vue
<template>
<div>
<el-button @click='giveDataToC'>B传值给C</el-button>
</div>
</template>
<script>
export default {
methods: {
giveDataToC () {
window.Event.$emit('data-B', 'data from A')
}
}
}
</script>
复制代码
C.vue
$on
监听了自定义事件 data-A
和 data-B
,因为有时不确定何时会触发事件,一般会在 mounted
或 created
钩子中来监听。
<template>
<div>
<div>来自A的数据:{{A}}</div>
<div>来自B的数据:{{B}}</div>
</div>
</template>
<script>
export default {
data () {
return {
A: '',
B: ''
}
},
mounted () {
window.Event.$on('data-A', val => {
this.A = val
})
window.Event.$on('data-B', val => {
console.log('form')
this.B = val
})
}
}
</script>
<style scoped>
div {
margin-bottom: 20px;
}
</style>
复制代码
注:要使用同一个 Event
方法三 $attrs / $listeners
vue 2.4 提供的多组件嵌套的传值,在中间不对数据做处理。
1 解释
$attrs
:包含了父作用域中不作为 prop
被识别 (且获取) 的特性绑定 (class
和 style
除外)。当一个组件没有声明任何 prop
时,这里会包含所有父作用域的绑定 (class
和 style
除外),并且可以通过 v-bind="$attrs"
传入内部组件——在创建高级别的组件时非常有用。
$listeners
:包含了父作用域中的 (不含 .native
修饰器的) v-on
事件监听器。它可以通过 v-on="$listeners"
传入内部组件——在创建更高层次的组件时非常有用。
2 例子
parent.vue
<template>
<div>
<strong>这是父组件</strong>
<child-a
:content=content
:shape=shape
:act=act
:lib=lib
title="前端所需"
@event1="ev1"
@event2="ev2"
></child-a>
</div>
</template>
<script>
import ChildA from '@/components/children/childA'
export default {
components: { ChildA },
data () {
return {
content: 'html',
shape: 'css',
act: 'Javascript',
lib: 'Vue'
}
},
methods: {
ev1 () {
console.log('ev1')
},
ev2 () {
console.log('ev2')
}
}
}
</script>
<style scoped>
div{
color: red;
margin: 30px;
}
</style>
复制代码
childA.vue
<template>
<div>
<p>这是组件A</p>
<p>content: {{ content }}</p>
<p>childA的$attrs: {{ $attrs }}</p>
<child-b
v-bind="$attrs"
v-on="$listeners"
></child-b>
</div>
</template>
<script>
import ChildB from '@/components/children/ChildB'
export default {
props: {
content: String
},
components: { ChildB },
inheritAttrs: false, // 可以关闭自动挂载到组件根元素上的没有在props声明的属性
created () {
console.log(this.$attrs);
},
mounted () {
this.$emit('event1')
console.log(this.$listeners)
}
}
</script>
<style scoped>
div{
padding-top: 30px;
color: green;
}
p{
line-height: 2
}
</style>
复制代码
childB.vue
<template>
<div>
<p>这是组件B</p>
<p>shape: {{ shape }}</p>
<p>childB的$attrs: {{ $attrs }}</p>
<child-c
v-bind="$attrs"
v-on="$listeners"
></child-c>
</div>
</template>
<script>
import ChildC from '@/components/children/ChildC'
export default {
props: {
shape: String
},
components: { ChildC },
inheritAttrs: false, // 可以关闭自动挂载到组件根元素上的没有在props声明的属性
created () {
console.log(this.$attrs);
}
}
</script>
<style scoped>
div{
padding-top: 30px;
}
p{
line-height: 2;
}
</style>
复制代码
childC.vue
<template>
<div>
<p>这是组件C:</p>
<p>childC的$attrs: {{ $attrs }}</p>
<p>lib:{{lib}}</p>
<p>title:{{title}}</p>
<el-button @click="event1">ev1</el-button>
</div>
</template>
<script>
export default {
props: {
lib: String,
title: String
},
inheritAttrs: false, // 可以关闭自动挂载到组件根元素上的没有在props声明的属性
created () {
console.log(this.$attrs);
},
mounted () {
},
methods: {
event1 () {
this.$listeners.event1()
}
}
}
</script>
<style scoped>
div{
padding-top: 30px;
color: yellowgreen;
}
p{
line-height: 2;
}
</style>
复制代码
综上,在父组件中定义数据以及事件,子组件、孙组件以及更多后代组件中的 $attrs
会继承除组件自身 props
中的属性,子组件、孙组件以及更多后代组件可以使用 this.$listener.事件名
来执行父组件中的事件。
方法四 provide / inject
这两个方法是在 vue 2.2.0 中新增的。
1 解释
provide
选项应该是一个对象或返回一个对象的函数。该对象包含可注入其子孙的属性。
inject
选项应该是:
- 一个字符串数组,或
- 一个对象,对象的 key 是本地的绑定名,value 是:
- 在可用的注入内容中搜索用的 key (字符串或 Symbol),或
- 一个对象,该对象的:
- from 属性是在可用的注入内容中搜索用的 key (字符串或 Symbol)
- default 属性是降级情况下使用的 value
祖先组件中通过 provider
来提供变量,然后在子孙组件中通过 inject
来注入变量。provide / inject API
主要解决了跨级组件间的通信问题,不过它的使用场景,主要是子组件获取上级组件的状态,跨级组件间建立了一种主动提供与依赖注入的关系。provide
和 inject
绑定并不是可响应的。这是刻意为之的。然而,如果你传入了一个可监听的对象,那么其对象的属性还是可响应的。
2 例子
父组件.vue
<template>
<div>
<child-a></child-a>
<el-button @click='change'>change</el-button>
</div>
</template>
<script>
import ChildA from '@/components/provide/ChildA'
export default {
provide: {
name: '这是一个 provide 的数据'
},
components: { ChildA },
methods: {
change () {
this.name = '修改一下看看子组件变不变'
}
}
}
</script>
<style scoped>
div{
margin: 50px;
}
</style>
复制代码
子组件.vue
<template>
<div>
来自父组件的内容: {{name}}
</div>
</template>
<script>
export default {
inject: ['name']
}
</script>
复制代码
可以看到,在父组件中定义 provide
,在子组件中使用 inject
就可以得到父组件 provide
中的值,但是我们点击修改父组件的值子组件的值并不会发生改变。
3 provide/inject
实现响应式
provide
祖先组件的实例,在子孙组件中注入依赖,这样就可以在子孙组件中直接修改祖先组件的实例的属性,不过这种方法有个缺点就是这个实例上挂载很多没有必要的东西比如 props
,methods
使用2.6最新API Vue.observable
优化响应式 provide
(推荐)
方法一例子:
父组件
<template>
<div>
<p>
父组件
<el-button @click="change('red')">改变Color</el-button>
</p>
<child-a></child-a>
</div>
</template>
<script>
import ChildA from "@/components/provide/ChildA";
export default {
data() {
return {
color: "blue"
};
},
provide() {
return {
theme: this // 方法一:提供祖先组件的实例
};
},
components: { ChildA },
methods: {
change(color) {
if (color) {
this.color = color;
} else {
this.color = this.color === "blue" ? "red" : "blue";
}
}
}
};
</script>
<style scoped>
div {
margin: 50px;
}
</style>
复制代码
子组件
<template>
<div :style="{ color: theme.color }">子组件A来自父组件的内容</div>
</template>
<script>
export default {
inject: {
theme: {
//函数式组件取值不一样
default: () => ({})
}
}
};
</script>
复制代码
方法二例子:
父组件
<template>
<div>
<p>
父组件
<el-button @click="change('red')">改变Color</el-button>
</p>
<child-a></child-a>
</div>
</template>
<script>
import ChildA from "@/components/provide/ChildA";
import Vue from "vue";
export default {
data() {
return {
color: "blue"
};
},
provide() {
this.theme = Vue.observable({
color: "blue"
});
return {
theme: this.theme
};
},
components: { ChildA },
methods: {
change(color) {
if (color) {
this.theme.color = color;
} else {
this.theme.color = this.theme.color === "blue" ? "red" : "blue";
}
}
}
};
</script>
<style scoped>
div {
margin: 50px;
}
</style>
复制代码
子组件
<template>
<div :style="{ color: theme.color }">子组件A来自父组件的内容</div>
</template>
<script>
export default {
inject: {
theme: {
//函数式组件取值不一样
default: () => ({})
}
}
};
</script>
复制代码
方法五 $parent / $children
与 ref
ref
:如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件实例$parent / $children
:访问父 / 子实例
父组件.vue
<template>
<div>
{{dataA}}
<child-a ref="childA"></child-a>
</div>
</template>
<script>
import ChildA from "@/components/ref/ChildA";
export default {
data() {
return {
dataA: "",
dataParent: "这是来自父组件的数据"
};
},
components: { ChildA },
mounted() {
const childA = this.$refs.childA;
console.log(childA.name1);
this.dataA = childA.name1;
console.log(this.$children[0].name2);
childA.operationA();
}
};
</script>
复制代码
子组件.vue
<template>
<div>{{dataFrom}}</div>
</template>
<script>
export default {
data() {
return {
name1: "这是来自子组件A的数据",
name2: "这也是来自子组件A的数据",
dataFrom: ""
};
},
mounted() {
this.dataFrom = this.$parent.dataParent;
},
methods: {
operationA() {
console.log("这是来自子组件的操作");
}
}
};
</script>
复制代码
这两种都是直接得到组件实例,使用后可以直接调用组件的方法或访问数据。它无法在兄弟或跨级组件之间通信。可以直接使用 $parent
获取父组件,从而访问父组件的数据以及方法;可以通过 $children
获取子组件(获取出来的是一个子组件的数组),从而访问子组件的数据以及方法。
方法六 $boradcast / $dispatch
这也是一对成对出现的方法,不过只是在Vue1.0中提供了,而Vue2.0被废弃了。
方法七 Vuex
Vue Components
:Vue
组件。HTML
页面上,负责接收用户操作等交互行为,执行dispatch
方法触发对应action
进行回应。dispatch
:操作行为触发方法,是唯一能执行action的方法。actions
:操作行为处理模块,由组件中的$store.dispatch('action 名称', data1)
来触发。然后由commit()
来触发mutation
的调用 , 间接更新state
。负责处理 Vue Components接收到的所有交互行为。包含同步/异步操作,支持多个同名方法,按照注册的顺序依次触发。向后台API请求的操作就在这个模块中进行,包括触发其他action
以及提交mutation
的操作。该模块提供了Promise
的封装,以支持action
的链式触发。commit
:状态改变提交操作方法。对mutation
进行提交,是唯一能执行mutation
的方法。mutations
:状态改变操作方法,由actions
中的commit('mutation 名称')
来触发。是Vuex
修改state
的唯一推荐方法。该方法只能进行同步操作,且方法名只能全局唯一。操作之中会有一些hook
暴露出来,以进行state
的监控等。state
:页面状态管理容器对象。集中存储 Vue components 中data
对象的零散数据,全局唯一,以进行统一的状态管理。页面显示所需的数据从该对象中进行读取,利用Vue的细粒度数据响应机制来进行高效的状态更新。getters
:state
对象读取方法。图中没有单独列出该模块,应该被包含在了render
中,Vue Components 通过该方法读取全局state
对象。
最后说一句
如果这篇文章对您有所帮助,或者有所启发的话,帮忙关注一下,您的支持是我坚持写作最大的动力,多谢支持。