Vue组件通信的其他方式
- 一、序言
- 二、组件通信的其他实现方式
- 2.1 访问根实例
- 2.2 访问父组件实例
- 2.3 访问子组件实例或子元素
- 2.4 provide和inject
一、序言
总结一下前面介绍的组件通信的3种方式:
- 父组件通过prop向子组件传递数据。
- 子组件通过自定义事件向父组件发起通知或进行数据传递。
- 子组件通过
<slot>
元素充当占位符,获取父组件分发的内容;也可以在子组件的<slot>
元素上使用v-bind指令绑定一个插槽prop,向父组件提供数据。 现在介绍组件通信的其他实现方式
二、组件通信的其他实现方式
2.1 访问根实例
在每一个根组件实例的子组件中,都可以通过$root
属性访问根实例。例如:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<div id="app">
<parent></parent>
</div>
<script src="https://unpkg.com/vue@next"></script>
<script>
const app = Vue.createApp({
data() {
return {
price: 188
}
},
computed: {
totalPrice(){
return this.price * 10;
}
},
methods: {
hello(){
return "Hello, Java无难事";
}
}
})
app.component('parent', {
template: '<child></child>'
})
app.component('child', {
methods: {
accessRoot(){
console.log("单价:" this.$root.price);
console.log("总价:" this.$root.totalPrice);
console.log(this.$root.hello());
}
},
template: '<button @click="accessRoot">访问根实例</button>'
})
app.mount('#app');
</script>
</body>
</html>
在浏览器单击“访问根实例”按钮,Console窗口中输出如下:
不管组件是根实例的子组件,还是更深层级的后代组件,$root属性总是代表了根实例。
2.2 访问父组件实例
与root类似,parent属性用于在一个子组件中访问父组件的实例,这可以替代父组件通过prop向子组件传数据的方式。 例如:
代码语言:javascript复制<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<div id="app">
<parent></parent>
</div>
<script src="https://unpkg.com/vue@next"></script>
<script>
const app = Vue.createApp({});
app.component('parent', {
data(){
return {
price: 188
}
},
computed: {
totalPrice(){
return this.price * 10;
}
},
methods: {
hello(){
return "Hello, Java无难事";
}
},
template: '<child></child>'
})
app.component('child', {
methods: {
accessParent(){
console.log("单价:" this.$parent.price);
console.log("总价:" this.$parent.totalPrice);
console.log(this.$parent.hello());
}
},
template: '<button @click="accessParent">访问父组件实例</button>'
})
app.mount('#app')
</script>
</body>
</html>
$parent
属性只能用于访问父组件实例,如果父组件之上还有父组件,那么该组件是访问不到的。
2.3 访问子组件实例或子元素
现在反过来,如果父组件要访问子组件实例,应该怎么办?在Vue.js中,父组件要访问子组件实例或子元素,可以给子组件或子元素添加一个特殊的属性ref
,为子组件或子元素分配一个引用ID,然后父组件就可以通过$refs
属性访问子组件实例或子元素。代码示例如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<div id="app">
<parent></parent>
</div>
<script src="https://unpkg.com/vue@next"></script>
<script>
const app = Vue.createApp({});
app.component('parent', {
mounted(){
// 访问子元素<input>,让其具有焦点
this.$refs.inputElement.focus();
// 访问子组件<child>的message数据属性
console.log(this.$refs.childComponent.message)
},
template: `
<div>
<input ref="inputElement"><br> <!--子元素-->
<child ref="childComponent"></child> <!-- 子组件-->
</div>`
})
app.component('child', {
data(){
return {
message: 'Java无难事'
}
},
template: '<p>{{message}}</p>'
})
app.mount('#app');
</script>
</body>
</html>
需要注意的是,refs属性只在组件渲染完成之后生效,并且它们都不是响应式的。要避免在模板和计算属性中访问refs。
2.4 provide和inject
root属性用于访问根实例,parent属性用于访问父组件实例,但如果组件嵌套的层级不确定,某个组件的数据或方法需要被后代组件所访问,又该如何实现呢?这时需要用到两个新的实例选项:provide和inject。provide选项允许指定要提供给后代组件的数据或方法,在后代组件中使用inject选项接收要添加到该实例中的特定属性。代码如下所示:
代码语言:javascript复制<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<div id="app">
<parent></parent>
</div>
<script src="https://unpkg.com/vue@next"></script>
<script>
const app = Vue.createApp({});
app.component('parent', {
data() {
return {
msg: 'Java无难事'
}
},
methods: {
sayHello(name){
console.log("Hello, " name);
}
},
provide(){
return {
// 数据message和sayHello方法可供后代组件访问
message: this.msg,
hello : this.sayHello
}
},
template: '<child/>',
})
app.component('child', {
// 接收message数据属性和hello方法
inject: ['message', 'hello'],
mounted(){
// 当自身的方法来访问
this.hello('zhangsan');
},
// 当自身的数据属性来访问
template: '<p>{{message}}</p>'
})
const vm = app.mount('#app')
</script>
</body>
</html>
使用provide
和inject
,父组件不需要知道哪些后代组件要使用它提供的属性,后代组件不需要知道被注入的属性来自哪里。
不过上述代码也存在一些问题。首先注入的message属性并不是响应式的,当修改父组件的msg数据属性时,message属性并不会跟着改变。这是因为默认情况下,provide/inject绑定并不是响应式的,可以通过传递ref属性或reactive对象更改这个行为。(后面再说吧)
其次,provice和inject将应用程序中的组件与它们当前的组织方式耦合起来,使得重构变得更加困难。
如果数据需要在多个组件中访问,并且能够相应更新,可以考虑真正的状态管理解决方案–Vuex
。