最近公司部分前端工程转 typeScript 的实践中,也尝试了 ts 的写法,诸如依赖注入、控制翻转、注解等。这些概念在 Java 等后端开发中应用可能更为广泛,但也不影响在前端领域的尝鲜。最终的写法如下;
依赖注入:
export default class DataQuery extends VueComponent {
@Inject('baseWebDataService')
public baseWebDataService!: BaseWebDataService;
@Inject('stationUtilService')
public stationUtilService!: StationUtilService;
@Inject('configService')
public configService!: ConfigService;
}
复制代码
网络请求:
export default class DataQuery extends VueComponent {
/**
* 获取基础信息
* @param params
* @param res
* @protected
*/
@HttpGet('/basicinfo/tree')
@HttpHeader(['Content-Type: application/json'])
@HttpBaseUrl('http://127.0.0.1')
protected async getBasicStationTreeData(@HttpParams() params: BasicStationForm, @HttpRes() res?: any) {
// 在此处可以有自己的业务数据处理逻辑
const queryKey: string = this.buildTempKeyByUrl(params);
if (Array.isArray(res.data) && res.data.length) {
RESOURCE_STATION_TREE_DATA.set(queryKey, res.data);
}
return res.data;
}
}
复制代码
今天的文章中来分享一下如何基于注解(装饰器)的方式来编写网络请求层,其中的代码已提交到 源码[1] 感兴趣的同学可以参考参考。
基于代码也打包了 npm 插件 @canyuegongzi/decorator-http-template
。
npm插件使用[2]
前置知识
装饰器
装饰器(Decorator)是一种与类(class)相关的语法,用来注释或修改类和类方法。许多面向对象的语言都有这项功能。
javaScript 对于元编程的支持尚不如 ts 完善,因此以 typeScript 来开发此插件。
ts 中装饰器大致分为类装饰器、属性装饰器、方法装饰器、参数装饰器。
类装饰器
此类装饰器可以是普通装饰器(无法传参)也可以是工厂模式(可以传参),工厂模式甚至可以直接重载整个类,ts 中的类型约束如下。
declare type ClassDecorator = <TFunction extends Function>(target: TFunction) => TFunction | void; 复制代码
普通装饰器:
此注解中 target 为 被修饰的类,在此可以扩展被修饰的类。
function log<T extends any>(target: T){
// console.log(target)
}
@log
class Test {}
复制代码
工厂模式
此类注解返回函数,可以通柯里化的方式传递参数。
function log(params: any){
return function<T extends any> (target: T) {}
}
@log('/login')
class Test {}
复制代码
属性装饰器
此类装饰器可以修饰类的成员属性,模式如类装饰器一样既可以是传统模式也可以采用工厂模式,此种装饰器在依赖注入中有大量的应用,ts 中的类型约束如下。
declare type PropertyDecorator = (target: Object, propertyKey: string | symbol) => void;
复制代码
export function Inject(id?: string): PropertyDecorator {
return (target: Record, propertyKey: string | symbol) => {
// 在此出可以实现与依赖注入相关的逻辑
};
}
class Test {
@Inject('configService')
public configService!: ConfigService;
}
复制代码
方法装饰器
此类装饰器可以重载类的成员函数,后续内容中会大量使用此类装饰器,此类装饰器存在三个参数,其一:target 为被修饰的类,其二:propertyKey是被修饰的成员函数的函数名,其三 descriptor 是被修饰的成员函数,在通常情况下可以通过 descriptor 参数重载此方法。ts 中的类型约束如下。
declare type MethodDecorator = (target: Object, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor) => TypedPropertyDescriptor | void;
复制代码
以下代码实现一个简单的接口缓存装饰器。
const ExpriesMemoryCache = new Map();
export function ApiCache(timeout = 120, keyArray = [],): MethodDecorator {
return (target, name, descriptor) => {
// 获取被修饰成员函数的原值
const originalMethod = descriptor.value;
// 重载此函数
descriptor.value = async function (...args) {
const argumentsArr = Array.from(arguments).concat(keyArray);
let key = JSON.stringify(name, Array.from(new Set(argumentsArr)));
let promise = ExpriesMemoryCache.get(key);
if (!promise) {
// 缓存中没有该接口的缓存数据时通过originalMethod.apply 方式调用原函数请求数据
promise = await originalMethod.apply(null, arguments).catch(async error => {
ExpriesMemoryCache.delete(key)
return Promise.reject(error);
});
// 数据请求完成后将数据存入缓存,下一次调用该方式时直接从缓存中获取数据
ExpriesMemoryCache.set(key, promise)
}
return promise;
};
return descriptor;
}
}
class Test {
@ApiCache(120, [])
public getData() {
}
}
复制代码
以上代码实现了一个简易版的接口缓存注解,实现较为简陋,做的好的话可以继续扩展此函数,实现诸如定时缓存、竞速缓存等功能。
参数装饰器
此类装饰器主要用来注解类成员函数中参数,该装饰器有存在参数,其一:target 为 被修饰函数的所属类,其二:propertyKey 为被修饰的函数名,其三:parameterIndex 为参数的索引(第几个参数),该中装饰器在服务端开发中有大量的应用,如 Controller 层中查询参数的应用,ts 类型约束如下。
declare type ParameterDecorator = (target: Object, propertyKey: string | symbol, parameterIndex: number) => void;
复制代码
class Test {
getInfo(@paramDecorator name: string, age: number) {
console.log(name, age);
}
}
function paramDecorator(target: any, method: string, paramIndex: number) {
// target 为 Test 类
// method 为 'getInfo'
// paramIndex 为 0 被修饰的参数的参数索引
console.log(target, method, paramIndex);
}
复制代码
reflect-metadata
Reflect Metadata 是 ES7 的一个提案,它主要用来在声明的时候添加和读取元数据。TypeScript 在 1.5+ 的版本已经支持它,你只需要:
npm i reflect-metadata --save
并在入口文件中 import 'reflect-metadata'
- 在 tsconfig.json 里配置
emitDecoratorMetadata
选项
defineMetadata
当作 Decorator 使用,当修饰类时,在类上添加元数据,当修饰类属性时,在类原型的属性上添加元数据。
Reflect.defineMetadata(ReqMethodQuery, parameterIndex, target, propertyKey);
复制代码
getMetadata
该 Api 用于获取target的元数据值, 会往原型链上找。
hasOwnMetadata
跟Object.prototype.hasOwnProperty类似, 是只查找对象上的元数据, 而不会继续向上查找原型链上的,如:
const reqQueryIndex: number = Reflect.getOwnMetadata(ReqMethodQuery, target, propertyKey);
复制代码
其他
关于 reflect-metadata 的概念在此处不作详细使用介绍,不过找了几篇文章,可以参考下:
开始
有之前的前置知识后,现在可以正式开始插件了。
网络请求方法装饰器
通过装饰器方式编写网络请求层。同样需要实现 Get、Post、Delete、Patch。
此处只以 Post、Get 为例,核心方法 createHttpDecoratorFunction 在下一步实现。
/**
* post post请求
* @param url 接口地址 /user/login
* @param data