简介
在构建大型复杂的应用程序时,依赖注入是一个很好的模式。构建这些应用程序的主要挑战是创建松散耦合的组件,而这正是依赖性管理最关键的地方。
本文将介绍依赖注入,其优点和缺点,以及如何在Vue项目中处理依赖注入。
什么是依赖性注入?
依赖注入是一种设计模式,其中类不允许创建依赖关系。相反,他们从外部来源请求依赖。这种设计模式强烈认为,一个类不应该静态地配置其依赖关系。
为什么要进行依赖注入?
既然我们可以将数据从父级组件向下传递到子级组件,为什么还要在Vue中使用依赖注入?
一些使用道具的经验会让你接触到道具钻取这个术语,这就是道具从组件树的一个部分传递到另一个部分的过程,这个过程要经过其他不需要数据的部分,而只是帮助它在树中传递。
RexComponent (Anyone needs my wallet address?)
├── TomComponent
├── PeterComponent
├── DurryComponent (yes I need it)
通过上面的片段,让我们考虑这样一个场景:RexComponent 有一个钱包地址要送出去,而DurryComponent 是唯一需要钱包地址的人。我们将不得不把钱包地址从RexComponent 到TomComponent 到PeterComponent ,最后到DurryComponent 。这导致了在TomComponent 和PeterComponent 中出现了多余的代码。
通过依赖注入,DurryComponent 将从RexComponent 接收钱包,而不需要通过TomComponent 和PeterComponent 。
为了处理Vue的依赖注入,提供和注入选项是开箱即用的。
要注入的依赖关系是由父组件使用provide属性提供的,如下所示。
//Parent component
<script lang="ts">
import {Component, Vue} from 'vue-property-decorator';
import Child from '@/components/Child.vue';
@Component({
components: {
Child
},
provide: {
'name': 'Paul',
},
})
export default class Parent extends Vue {
}
</script>
提供的依赖被注入到使用注入属性的子组件中。
<template>
<h1> My name is {{name}}</h1>
</template>
<script lang="ts">
import {Component, Inject, Vue} from 'vue-property-decorator';
@Component({})
export default class Child extends Vue {
@Inject('name')
name!: string; // non-null assertion operator
}
</script>
vue-property-decorator 也暴露了用于声明提供者的@Provide 装饰器。
使用@Provide 装饰器,我们可以使依赖在父组件中可用。
//Parent component
export default class ParentComponent extends Vue {
@Provide("user-details") userDetails: { name: string } = { name: "Paul" };
}
同样地,依赖性也可以被注入到子组件中。
//Child component
<script lang="ts">
import {Component, Inject, Vue} from 'vue-property-decorator';
@Component({})
export default class ChildComponent extends Vue {
@Inject('user-details')
user!: { name: string };
}
</script>
提供者层次结构
提供者层次规则指出,如果一个组件的依赖树中的多个提供者使用了相同的提供者键,那么离子组件最近的父类的提供者将优先于层次结构中的其他提供者。
为了便于理解,让我们考虑下面的片段。
FatherComponent
├── SonComponent
├── GrandsonComponent
//Father component
<script lang="ts">
import {Component, Vue} from 'vue-property-decorator';
import SonComponent from '@/components/Son.vue';
@Component({
components: {
SonComponent
},
provide: {
'family-name': 'De Ekongs',
},
})
export default class FatherComponent extends Vue {
}
</script>
在上面的片段中,family-name 依赖关系是由FatherComponent 提供的。
//Son component
<script lang="ts">
import {Component, Vue} from 'vue-property-decorator';
import GrandsonComponent from '@/components/Grandson.vue';
@Component({
components: {
GrandsonComponent
},
provide: {
'family-name': 'De Royals',
},
})
export default class SonComponent extends Vue {
}
</script>
在上面的片段中,SonComponent 覆盖了之前由FatherComponent 提供的family-name 依赖关系。
//Grand son Component
<template>
<h1> Our family name is {{familyName}}</h1>
</template>
<script lang="ts">
import {Component, Inject, Vue} from 'vue-property-decorator';
@Component({})
export default class Child extends Vue {
@Inject('family-name')
familyName!: string; // non-null assertion operator
}
</script>
正如你所猜测的,De Royals 将被渲染在GrandsonComponent 的模板中。
在一些复杂的Vue项目中,你可能会避免覆盖依赖关系以实现代码库的一致性。在这种情况下,覆盖依赖关系被视为一种限制。
幸运的是,JavaScript为我们提供了ES6符号,作为对与具有相同键的多个提供者相关的缺点的补救措施。
根据MDN,"符号通常被用来为一个对象添加独特的属性键,这些属性键不会与任何其他代码可能添加到该对象的键相冲突,而且这些属性键被隐藏在其他代码通常用来访问该对象的任何机制中。"
换句话说,每个符号都有一个独特的身份。
Symbol('foo') === Symbol('foo') // false
我们可以使用ES6的Symbol ,而不是像我们之前的代码那样,在提供方和注入方使用相同的字符串密钥。这将确保没有依赖性被另一个依赖性所覆盖。
export const FAMILY = {
FAMILY_NAME: Symbol('FAMILYNAME'),
};
依赖性注入的优点
- 提高代码的可重用性
- 通过模拟/存根注入的依赖关系,简化了应用程序的单元测试
- 减少模板代码,因为依赖是由其注入组件初始化的
- 解除组件逻辑的束缚
- 使得扩展应用类更加容易
- 加强应用程序的配置
依赖性注入的注意事项
- Vue的依赖注入不支持构造器注入。这对使用基于类的组件的开发者来说是个很大的缺陷,因为构造函数不会初始化组件类的属性。
- 许多编译时的错误被推送到运行时
- 使用Vue的依赖注入,代码重构会非常繁琐
- Vue的依赖注入不是反应式的。
总结
在这篇文章中,我们对Vue中的依赖注入有了基本了解。我们走过了与具有相同键的多个提供者有关的缺点,同时我们还使用ES6符号实现了对该缺点的补救。
The postDependency injection in Vue:优点和注意事项》首次出现在LogRocket博客上。