目录
五、使用 vue-ref 插件结合 provide 和 inject 获取组件的实例
前言
vue 组件之间的通信方式大概有 9 种:
- props 和 $emit
- $refs
- $attrs 和 $listeners
- eventBus
- provide 和 inject
- v-model
- $parent 和 $children
- boradcast 和 dispatch
- vuex 处理组件之间的数据交互
一、props 和 $emit
父传子——props
子传父——$emit
1、一般的通信
Vue.component("child", {
data() {
return {
mymessage: this.message,
};
},
template: `
<div>
<input type="text" v-model="mymessage" @input="passData(mymessage)"> </div>
`,
props: ["message"], //得到父组件传递过来的数据
methods: {
passData(val) {
//触发父组件中的事件
this.$emit("getChildData", val);
},
},
});
Vue.component("parent", {
template: `
<div>
<p>this is parent compoent!</p>
<child :message="message" v-on:getChildData="getChildData"></child>
</div>
`,
data() {
return {
message: "hello",
};
},
methods: {
//执行子组件触发的事件
getChildData(val) {
console.log(val);
},
},
});
var app = new Vue({
el: "#app",
template: `
<div>
<parent></parent>
</div>
`,
});
上述代码中,父组件传递了message数据给子组件,并且通过v-on绑定了一个getChildData事件来监听子组件的触发事件;子组件通过props得到相关的message数据,最后通过this.$emit触发了getChildData事件。
假设有这么一个场景:私人订制一只宠物。父组件是具有定制宠物的给用户的功能,子组件具有决定定制什么宠物的功能。那么此时,子组件在调用父组件的定制宠物的给用户的功能时,就需要给选定一种宠物,这就需要传递第二个参数了。
2、子组件更新父组件的变量
二、$attrs 和 $listeners
问题:如果父组件A下面有子组件B,组件B下面有组件C,这时如果组件A想传递数据给组件C怎么办呢?
可以借助 $attrs 和 $listeners 来实现 “隔代传值”。
Vue.component("C", {
template: `
<div>
<input type="text" v-model="$attrs.messagec" @input="passCData($attrs.messagec)"> </div>
`,
methods: {
passCData(val) {
//触发父组件A中的事件
this.$emit("getCData", val);
},
},
});
Vue.component("B", {
data() {
return {
mymessage: this.message,
};
},
template: `
<div>
<input type="text" v-model="mymessage" @input="passData(mymessage)">
<!-- C组件中能直接触发getCData的原因在于 B组件调用C组件时 使用 v-on 绑定了$listeners 属性 -->
<!-- 通过v-bind 绑定$attrs属性,C组件可以直接获取到A组件中传递下来的props(除了B组件中props声明的) -->
<C v-bind="$attrs" v-on="$listeners"></C>
</div>
`,
props: ["message"], //得到父组件传递过来的数据
methods: {
passData(val) {
//触发父组件中的事件
this.$emit("getChildData", val);
},
},
});
Vue.component("A", {
template: `
<div>
<p>this is parent compoent!</p>
<B :messagec="messagec" :message="message" v-on:getCData="getCData" v-on:getChildData="getChildData(message)"></B>
</div>
`,
data() {
return {
message: "hello",
messagec: "hello c", //传递给c组件的数据
};
},
methods: {
getChildData(val) {
console.log("这是来自B组件的数据");
},
//执行C子组件触发的事件
getCData(val) {
console.log("这是来自C组件的数据:" + val);
},
},
});
var app = new Vue({
el: "#app",
template: `
<div>
<A></A>
</div>
`,
});
三、eventBus——$emit 和 $on
这种方法通过一个空的Vue实例作为中央事件总线(事件中心),用它来触发事件和监听事件,巧妙而轻量地实现了任何组件间的通信,包括父子、兄弟、跨级。
$emit 和 $on 的实现:新建一个Vue事件bus对象,然后通过bus.$emit触发事件,bus.$on监听触发的事件。
Vue.component("brother1", {
data() {
return {
mymessage: "hello brother1",
};
},
template: `
<div>
<p>this is brother1 compoent!</p>
<input type="text" v-model="mymessage" @input="passData(mymessage)">
</div>
`,
methods: {
passData(val) {
//触发全局事件globalEvent
bus.$emit("globalEvent", val);
},
},
});
Vue.component("brother2", {
template: `
<div>
<p>this is brother2 compoent!</p>
<p>brother1传递过来的数据:{{brothermessage}}</p>
</div>
`,
data() {
return {
mymessage: "hello brother2",
brothermessage: "",
};
},
mounted() {
//绑定全局事件globalEvent
bus.$on("globalEvent", (val) => {
this.brothermessage = val;
});
},
});
//中央事件总线
var bus = new Vue();
var app = new Vue({
el: "#app",
template: `
<div>
<brother1></brother1>
<brother2></brother2>
</div>
`,
});
四、provide 和 inject(跨组件通信)
provide 和 inject 是成对出现的。在上层组件中通过 provide 来提供变量,然后在下层组件中通过 inject 来注入变量。用于父组件向子孙组件传递数据。
1、认识 provide 和 inject
provide 选项可以是:
- 一个对象。
- 一个函数,该函数的返回值是一个对象。
inject 选项可以是:
- 一个字符串数组。
- 一个对象。此时:
- 对象的 key 是当前组件将要使用的变量名。
- 对象的 value 可以是:
- provide 里提供的属性,以及。
- 一个对象,该对象包括两个属性:
- from 属性:provide 里提供的属性。
- default 属性:默认值。
provide 和 inject 绑定并不是可响应的。这是刻意为之的。然而,如果你传入了一个可监听的对象,那么其对象的属性还是可响应的。
2、provide 和 inject 的使用
(1)、传递普通的数据
父组件:
<template>
<s-p-r-v2 />
</template>
<script>
import SPRV2 from './components/s-p-r-v2.vue'
export default {
components: {
SPRV2
},
provide() {
return {
str: 'hello'
}
}
}
</script>
子组件:
<template>
<!-- <div>{{ str }}</div> -->
<div>{{ msg }}</div>
</template>
<script>
export default {
// 方式一
// inject: ['str'],
// 方式二
// inject: {msg: 'str' }
// 方式三
inject: {
msg: {
from: 'str',
default: ''
}
}
}
</script>
(2)、传递异步的数据
父组件:
<template>
<s-p-r-v2 />
</template>
<script>
import SPRV2 from './components/s-p-r-v2.vue'
export default {
components: {
SPRV2
},
provide() {
return {
flag: () => this.flag
}
},
data() {
return {
flag: false
}
},
mounted() {
setTimeout(() => {
this.flag = true
}, 3000)
}
}
</script>
子组件:
<template>
<div>{{ newStatus }}</div>
</template>
<script>
export default {
// 方式一
// inject: ['flag'],
// 方式二
// inject: { status: 'flag' },
// 方式三
inject: {
status: {
from: 'flag',
default: () => false
}
},
computed: {
newStatus() {
// return this.flag()
return this.status()
}
}
}
</script>
【注意】小型状态管理,可以使用 provide/inject。大型状态管理,建议使用 vuex。
【扩展】
五、使用 vue-ref 插件结合 provide 和 inject 获取组件的实例
可以使用 ref 来获取跨级组件实例。
安装:vue-ref
npm i vue-ref -S
然后,在main.js中写入下面的代码:
import ref from 'vue-ref';
Vue.use(ref, { name: 'getRef' });// 这里之所以重命名,是为了避免与vue预留的v-ref指令冲突
假设,已经创建了vue实例,并且在其中引入了index.vue,或者配置好了vue-router。
// index.vue
<template>
<div>
<ChildrenA />
</div>
</template>
<script>
import ChildrenA from "./component/ChildrenA1";
export default {
components: {
ChildrenA
}
};
</script>
子组件:A
// ChildrenA1.vue
<template>
<div>
<h1>A 结点</h1>
<button @click="getEH3Ref">获取E h3 Ref</button>
<ChildrenC />
</div>
</template>
<script>
import ChildrenC from "./ChildrenC2";
export default {
components: {
ChildrenC,
},
provide() {
return {
/**
* @name:子组件的组件名称
* @ref:子组件中 ref 的名字
*
* 主动通知——缓存子组件的 ref
**/
setChildrenRef: (name, ref) => {
this[name] = ref;
},
/**
* @name:其他子组件的组件名称
*
* 主动获取——获取其他子组件的实例
**/
getChildrenRef: name => {
return this[name];
},
// 获取上层组件的实例
getRef: () => {
return this;
}
};
},
methods: {
getEH3Ref() {
console.log(this.childrenE);
}
}
};
</script>
子组件的子组件:E
// childrenE.vue
<template>
<div>
<h3 v-getRef="c => setChildrenRef('childrenE', c)">E 结点</h3>
<button @click="getARef">获取组件 A 的实例</button>
<button @click="getHRef">获取组件 H 的实例</button>
</div>
</template>
<script>
export default {
inject: {
setChildrenRef: {
default: null
},
getParentRef: {
from: "getRef",
default: () => {}
},
getParentChildrenRef: {
from: "getChildrenRef",
default: () => {}
}
},
methods: {
// 获取上层组件的实例
getARef() {
console.log(this.getParentRef());
},
// 获取其他子组件的实例
getHRef() {
console.log(this.getParentChildrenRef("childrenH"));
}
}
};
</script>
子组件的子组件:H
<template>
<div>
<h3>H 结点</h3>
</div>
</template>
六、v-model
父组件通过v-model传递值给子组件时,会自动传递一个value的prop属性,在子组件中通过this.$emit(‘input’,val)自动修改v-model绑定的值。
Vue.component("child", {
props: {
value: String, //v-model会自动传递一个字段为value的prop属性
},
data() {
return {
mymessage: this.value,
};
},
methods: {
changeValue() {
this.$emit("input", this.mymessage); //通过如此调用可以改变父组件上v-model绑定的值
},
},
template: `
<div>
<input type="text" v-model="mymessage" @change="changeValue">
</div>
`,
});
Vue.component("parent", {
template: `
<div>
<p>this is parent compoent!</p>
<p>{{message}}</p>
<child v-model="message"></child>
</div>
`,
data() {
return {
message: "hello",
};
},
});
var app = new Vue({
el: "#app",
template: `
<div>
<parent></parent>
</div>
`,
});
七、$parent、$children 和 $refs
有时候我们需要父组件直接访问子组件、子组件直接访问父组件,或者子组件访问根组件:
- 父组件访问子组件:使用 $children 或 $refs
- 子组件访问父组件:使用 $parent
1、$children 和 $rfes
this.$children 是一个数组类型,它包含了所有子组件对象,可以通过循环它拿到所有子组件数据。
比如:this.$children[2] 来拿到第三个子组件的数据。
但是这么做有一个问题:比如开发时突然在这三个子组件中又插入了一个子组件(可能相同,也可能不同),这时候 this.$children[2] 就不再是我们需要的了。怎么办呢?使用 $rfes。
2、$parent(不建议使用)
$parent 是当前组件树的根 Vue 实例。如果当前实例没有父实例,此实例将会是其自己。该属性只读。
不建议使用 $parent,因为它的耦合度很高,容易发生一些错误。
3、案例
Vue.component("child", {
props: {
value: String, //v-model会自动传递一个字段为value的prop属性
},
data() {
return {
mymessage: this.value,
};
},
methods: {
changeValue() {
this.$parent.message = this.mymessage; //通过如此调用可以改变父组件的值
},
},
template: `
<div>
<input type="text" v-model="mymessage" @change="changeValue">
</div>
`,
});
Vue.component("parent", {
template: `
<div>
<p>this is parent compoent!</p>
<button @click="changeChildValue">test</button >
<child></child>
</div>
`,
methods: {
changeChildValue() {
this.$children[0].mymessage = "hello";
},
},
data() {
return {
message: "hello",
};
},
});
var app = new Vue({
el: "#app",
template: `
<div>
<parent></parent>
</div>
`,
});
八、vuex 处理组件之间的数据交互
如果业务逻辑复杂,很多组件之间需要同时处理一些公共的数据,这个时候才有上面这一些方法可能不利于项目的维护,vuex的做法就是将这一些公共的数据抽离出来,然后其他组件就可以对这个公共数据进行读写操作,这样达到了解耦的目的。
详情可参考:Vuex