在Typescript中,有一个非常优秀的依赖注入框架:TypeDI,简洁api设计,可以让使用者一学就会。我们来一起看看typedi是如何工作的以及我们改如何使用它。
一、注解:@Service()
@Service
是一个类注解,它会用Container.set()
将获取到的类存储到Container.instance
中。
使用方式
@Service()
class User {
}
源代码
function Service(optionsOrServiceIdentifier) {
return targetConstructor => {
const serviceMetadata = {
id: targetConstructor,
// TODO: Let's investigate why we receive Function type instead of a constructable.
type: targetConstructor,
factory: undefined,
multiple: false,
global: false,
eager: false,
transient: false,
value: empty_const_1.EMPTY_VALUE,
};
if (optionsOrServiceIdentifier instanceof token_class_1.Token || typeof optionsOrServiceIdentifier === 'string') {
/** We received a Token or string ID. */
serviceMetadata.id = optionsOrServiceIdentifier;
}
else if (optionsOrServiceIdentifier) {
/** We received a ServiceOptions object. */
serviceMetadata.id = optionsOrServiceIdentifier.id || targetConstructor;
serviceMetadata.factory = optionsOrServiceIdentifier.factory || undefined;
serviceMetadata.multiple = optionsOrServiceIdentifier.multiple || false;
serviceMetadata.global = optionsOrServiceIdentifier.global || false;
serviceMetadata.eager = optionsOrServiceIdentifier.eager || false;
serviceMetadata.transient = optionsOrServiceIdentifier.transient || false;
}
container_class_1.Container.set(serviceMetadata);
};
}
源代码说明
- 接收一个参数
optionsOrServiceIdentifier
,这个参数可以是字符串、Token类或者一个对象 - 先默认给你设置一个
metadata
,这里面的id默认为注解的构造函数 - 通过传入的参数修改默认配置
- 将
metadata
存入容器Container.instance
中。
二、注解:@Inject()
@Inject
是一个属性和参数注解,它会将target
、propertyKey
、index
和一个获取示例的回调函数value
存储到Container.handlers
中。
使用方式
@Service()
class Login {
@Inject()
user: User;
}
通过这种方式我们就能直接在Login中访问user的实例。
源代码
function Inject(typeOrIdentifier) {
return function (target, propertyName, index) {
const typeWrapper = resolve_to_type_wrapper_util_1.resolveToTypeWrapper(typeOrIdentifier, target, propertyName, index);
/** If no type was inferred, or the general Object type was inferred we throw an error. */
if (typeWrapper === undefined || typeWrapper.eagerType === undefined || typeWrapper.eagerType === Object) {
throw new cannot_inject_value_error_1.CannotInjectValueError(target, propertyName);
}
container_class_1.Container.registerHandler({
object: target,
propertyName: propertyName,
index: index,
value: containerInstance => {
const evaluatedLazyType = typeWrapper.lazyType();
/** If no type was inferred lazily, or the general Object type was inferred we throw an error. */
if (evaluatedLazyType === undefined || evaluatedLazyType === Object) {
throw new cannot_inject_value_error_1.CannotInjectValueError(target, propertyName);
}
return containerInstance.get(evaluatedLazyType);
},
});
};
}
Inject
接收一个可选参数typeOrIdentifier
。注解会带三个值给你,其中index
仅在注解在函数的参数中时才有效。
看到这里,请大家先静思一个问题:
Inject
如何知道你要注入的属性对应的构造类的?我们对@Inject
注解的属性只有声明,并没有赋值。大家要清楚,这个是ts,ts最终会编译为js,js是没有定义类型这个概念的。
答案揭晓
主要依靠的是reflect-metadata
库。Reflect Metadata 主要用来在声明的时候添加和读取元数据。通过这种方式给对象添加额外的信息,是不会影响对象的结构的。在编译为js代码后,我们可以看到他的一些基础元数据的添加
ts代码:
@Service()
class Login {
@Inject()
user: User
}
编译后js代码
var Login = (function () {
function Login() {
}
__decorate([
Inject(),
__metadata("design:type", User)
], Login.prototype, "user", void 0);
Login = __decorate([
Service()
], Login);
return Login;
}());
我们可以看到他对Login类中的user属性设置了一个元数据design:type
,且值为User
。
相信聪明的你应该已经明白接下是怎么做的了。没错,我们只要通过design:type
这个key去获取就可以到对应的元数据User
。
而typedi获取的源代码也就在resolveToTypeWrapper
中:
function resolveToTypeWrapper(typeOrIdentifier, target, propertyName, index) {
/** 省略代码 */
if (!typeOrIdentifier && propertyName) {
const identifier = Reflect.getMetadata('design:type', target, propertyName);
typeWrapper = { eagerType: identifier, lazyType: () => identifier };
}
/** 省略代码 */
return typeWrapper;
}
接下来我们就只要将这个数据(target、propertyName、index,value)存储到缓存handles中,等待别人来获取就可以。
三、Container.get()
Container.get() 只一个类方法,他接收一个构造函数、字符串key或者Token
使用方式
Container.get(Login)
注:获取的类必须使用的
@Service()
否则会获取不到
- typedi 会去Container的缓存instance中寻找Login
- 找到后会去实例化Login,然后将他存储到缓存中,保证下次获取。
- 实例化的时候会去handles中找到所有使用了@Inject的属性,将其实例赋值到对应的属性。
四、注意事项
- 由于使用了reflect-metadata库,而这个库有个要求就是必须在启动代码的第一行使用
import 'reflect-metadata'
,如果没使用会遇到错误:Property 'getMetadata' does not exist on type 'typeof Reflect'.
- 所有需要通过
@Inject
、Container.get
去获取实例的类,必须使用@Service
。 - 对于
@Inject
,在他当前的类上必须使用@Service
,且new className()
, 将会是无效的。