关于 vue 中的 provide/inject 这一主题,它可以被看作是一种高级的依赖注入机制,允许跨层级组件实现状态共享,从而提高代码的可维护性和扩展性。在这篇文章中,我将从原理到实战带大家由浅入深探究这个机制的底层原理和具体使用方法,无论你是 vue 的初学者还是经验丰富的开发者,相信本文都将对你有所帮助。
在 vue
中组件之间交互通信有很多种方式,props
传值、emit
传值、bus
传值等等,相信大多数同学对此都不陌生,不太清楚的同学也可以 『点此』 查看博主往期的文章,其中有详细的讲解。话说回来,当组件的层次结构比较深时,props
和 emit
就没什么作用了。这个时候,vue
提出了 Provide / Inject
。
文章开头这张图,很好地诠释了 provide/inject
的核心,但是可能很多同学并没有看出其中端倪,别急,文章最后,还是这张图,你一定会恍然大悟。
provide/inject
这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在其上下游关系成立的时间里始终生效。通俗说就是可以用父组件给祖孙组件进行传值,也就是可以隔代传值,不论子组件有多深,只要调用了 inject
那么就可以注入 provider
中的数据,而不是局限于只能从当前父组件的 props
属性来获取数据,这也是 provide/inject
最大的特性。
provide 提供变量
provide
是一个对象,或者是一个返回对象的函数。里面呢就包含要给子孙后代的东西,也就是属性和属性值。
inject 注入变量
inject
是一个字符串数组,或者是一个对象。属性值可以是一个对象,包含 form
和 default
默认值。
provide
Object | () => Object
inject
Array<string> | { [key: string]: string | Symbol | Object }
data->provide->created(在这个阶段$el还未生成,在这先处理privide的逻辑,子孙组件才可以取到inject的值)->mounted
注意:
provide
和 inject
绑定并不是可响应的。这是刻意为之的。然而,如果你传入了一个可监听的对象,那么其对象的 property
还是可响应的。根据官方要求,我们只需要传入对象过去,并且对象是响应式的,定义在 data
或者计算属性里都可以。
相信看完上面的内容,你已经对 provide
和 inject
有了初步的认识和了解,但是说了这么多,终究都是理论,秉持着实践出真知的原则,下面我们进行代码实践。
在父组件中
provide
提供变量
文件目录:src/views/monitors/index.vue
<template>
<div>
<Child>Child>
div>
template>
<script>
import Child from "./seed/index";
export default {
components: {
Child,
},
provide: {
message: "传给孙组件的值",
},
data() {
return {};
},
};
script>
在子组件中,我们不使用任何父组件的信息
文件目录:src/views/monitors/seed/index.vue
<template>
<div>
<el-dialog :visible.sync="centerDialogVisible" width="30%" center>
<grandson />
el-dialog>
div>
template>
<script>
import grandson from "./grandson/index";
export default {
components: {
grandson,
},
data() {
return {
centerDialogVisible: true,
};
},
};
script>
在孙组件中,使用
inject
来注入
文件目录:src/views/monitors/seed/grandson/index.vue
<template>
<div>
{{message }}
div>
template>
<script>
export default {
inject: ["message"],
data() {
return {};
},
};
script>
页面效果
上面的例子我们是在 provide
中定义的一个字符串,那如果要传 data
里的一个属性呢?这个时候就需要稍微修改一下组件,provide
需要用 returen
的写法。
需要在父组件中修改
文件目录:src/views/monitors/index.vue
<template>
<div>
<Child>Child>
div>
template>
<script>
import Child from "./seed/index";
export default {
components: {
Child,
},
provide() {
return {
message: this.message,
};
},
data() {
return {
message: "传给孙组件的值",
};
},
};
script>
子组件和孙组件依旧保持不变。
页面效果
文章开头我们提到了 provide
和 inject
绑定并不是可响应的。 那如何变成响应式的呢,再简单改一下。
还是在父组件中修改
文件目录:src/views/monitors/index.vue
<template>
<div>
<Child>Child>
div>
template>
<script>
import Child from "./seed/index";
export default {
components: {
Child,
},
provide() {
return {
message: this.obj,
};
},
data() {
return {
obj: {
num: 0,
},
};
},
created() {
setInterval(() => {
this.obj.num++;
}, 1000);
},
};
script>
子组件依旧不变
孙组件中稍加修改
文件目录:src/views/monitors/seed/grandson/index.vue
<template>
<div>
{{message.num}}
div>
template>
<script>
export default {
inject: ["message"],
data() {
return {};
},
};
script>
页面效果
注意:
传过去的必须是可监听的对象,其他类型都不行。
上面的操作,我们只是取了 data
中的一个对象,当然,你可以直接传一个 this
过去,这样孙组件就会获得爷爷组件的实例对象,且这种方式也是响应式的。
仍然是在父组件中修改
文件目录:src/views/monitors/index.vue
<template>
<div>
<Child>Child>
div>
template>
<script>
import Child from "./seed/index";
export default {
components: {
Child,
},
provide() {
return {
message: this,
};
},
data() {
return {
obj: {
num: 0,
},
};
},
created() {
setInterval(() => {
this.obj.num++;
}, 1000);
},
};
script>
子组件依旧不变
孙组件中再次稍加修改
文件目录:src/views/monitors/seed/grandson/index.vue
<template>
<div>
{{message.obj.num}}
div>
template>
<script>
export default {
inject: ["message"],
data() {
return {};
},
};
script>
页面效果
通过一系列的理论+实践,现在我们回过头再来看文章开头的这张图是不是就很清晰了,但是可能有人问了,难道 provide/inject
就没有什么缺点吗?咱们接着往下看。
大家都知道,在项目中通常追求有清晰的数据流向和合理的组件层级关系,以便于调试和维护,然而 provide
和 inject
支持任意层级都能访问的特性,导致数据追踪比较困难,你压根不知道是哪一个层级声明了 provide
,或者不知道哪一个层级或若干个层级使用了 inject
,后期容易造成比较大的维护成本。因此,provide
和 inject
在常规应用下并不建议使用,vue
更建议使用 vuex
解决。但是在做组件库开发时,不对 vuex
进行依赖,且不知道用户使用环境的情况下可以很好的使用 provide
和 inject
。官方也着重说明 provide
和 inject
主要为高阶插件/组件库提供用例,并不推荐直接用于应用程序代码中。
⭐ vue组件传值:从基础到进阶