装饰器
- 启用装饰器支持
- 类装饰器
- 定义并使用装饰器
- 对比不使用装饰器
- 装饰器叠加
- 实现消息提示统一响应
- 装饰器工厂
- 方法装饰器
- 登录状态验证
- 数据请求
- 属性装饰器
- 大小写转换
- 元数据
- 安装依赖
- 基础用法
- 参数装饰器
- 验证参数是否为必填项
启用装饰器支持
修改 tsconfig.json
{"experimentalDecorators": true, // 启用装饰器的实验性支持"emitDecoratorMetadata": true, // 为源文件中的修饰声明发出设计类型元数据
}
类装饰器
定义并使用装饰器
const Decorator: ClassDecorator = (target: Function) => {console.log(target);target.prototype.sayName = () => {console.log((<any>target).firstName, (<any>target).lastName);}}@Decorator // [Function: Person] { firstName: 'Prosper', lastName: 'Lee' }
class Person {public static firstName: string = "Prosper";public static lastName: string = "Lee";
}const person: Person = new Person();
(<any>person).sayName(); // Prosper Lee
对比不使用装饰器
class Animal {public static firstName: string = "Dog";public static lastName: string = "Small";
}
Decorator(Animal); // [Function: Animal] { firstName: 'Dog', lastName: 'Small' }const animal = new Animal();
(<any>animal).sayName(); // Dog Small
装饰器叠加
const Decorator1: ClassDecorator = (target: Function) => {console.log('装饰器1', target);target.prototype.sayName = () => {console.log((<any>target).firstName, (<any>target).lastName);}}const Decorator2: ClassDecorator = (target: Function) => {console.log('装饰器2', target);target.prototype.sayHello = () => {console.log('Hello', (<any>target).firstName, (<any>target).lastName);}}@Decorator1
@Decorator2
class Person {public static firstName: string = "Prosper";public static lastName: string = "Lee";
}/*** 运行结果* 先 -> 装饰器2 [Function: Person] { firstName: 'Prosper', lastName: 'Lee' }* 后 -> 装饰器1 [Function: Person] { firstName: 'Prosper', lastName: 'Lee' }*/const person: Person = new Person();
(<any>person).sayName(); // Prosper Lee
(<any>person).sayHello(); // Hello Prosper Lee
实现消息提示统一响应
enum MessageType {log = 'log',info = 'info',warn = 'warn',error = 'error',
}interface MessageData {type: MessageType;message: string;
}const MessageDecorator: ClassDecorator = (target: Function) => {console.log(target);target.prototype.$message = (data: MessageData) => {console[data.type](data.message);}
}@MessageDecorator
class Person {public sayMessage() {(<any>this).$message({ type: MessageType.log, message: 'Log Log Log !!!' });(<any>this).$message({ type: MessageType.info, message: 'Info Info Info !!!' });(<any>this).$message({ type: MessageType.warn, message: 'Warn Warn Warn !!!' });(<any>this).$message({ type: MessageType.error, message: 'Error Error Error !!!' });}
}const person: Person = new Person();
(<any>person).sayMessage();
装饰器工厂
enum MessageType {log = 'log',info = 'info',warn = 'warn',error = 'error',
}const MessageDecorator = (type: MessageType): ClassDecorator => {return (target: Function) => {console.log(target);target.prototype.$message = (message: string) => {console[type](message);}}
}@MessageDecorator(MessageType.log)
class Person {public sayMessage() {(<any>this).$message('Log Log Log !!!');}
}const person: Person = new Person();
(<any>person).sayMessage(); // Log Log Log !!!
方法装饰器
const FuncDecorator: MethodDecorator = (target: Object, propertyKey: string | symbol, descriptor: PropertyDescriptor) => {console.log('静态成员的类的构造函数 / 实例成员的类的原型', target);console.log('成员的名称', propertyKey);console.log('成员的属性描述符', descriptor);const method = descriptor.value;// 通过装饰器修改原有方法descriptor.value = (...args: any[]) => {console.log(`修改了方法: ${propertyKey.toString()}`, args);method.apply(target, args);}
}class FuncClass {/*** @FuncDecorator* 静态成员的类的构造函数 / 实例成员的类的原型 ƒ FuncClass() { }* 成员的名称 funcA* 成员的属性描述符 {writable: true, enumerable: true, configurable: true, value: ƒ}*/@FuncDecoratorpublic static funcA(a1: string, a2: number) {console.log(a1, a2);}/*** @FuncDecorator* 静态成员的类的构造函数 / 实例成员的类的原型 {funcB: ƒ, constructor: ƒ}* 成员的名称 funcB* 成员的属性描述符 {writable: true, enumerable: true, configurable: true, value: ƒ}*/@FuncDecoratorpublic funcB(b1: boolean) {console.log(b1);}}/*** 结果:* 修改了方法: funcA ['Lee', 20, 1, 2, 3]* Lee 20*/
FuncClass.funcA('Lee', 20, 1, 2, 3);/*** 结果:* 修改了方法: funcB [true, 1, 2, 3]* true*/
const func = new FuncClass();
func.funcB(true, 1, 2, 3);
登录状态验证
const ValidateTokenDecorator: MethodDecorator = (target: Object, propertyKey: string | symbol, descriptor: PropertyDescriptor) => {const method = descriptor.value;descriptor.value = (...args: any[]) => {// 登录验证相关代码if (localStorage.getItem('token')) {alert('您已登录,无需重复登录,正在跳转...');} else {method.apply(target, args);}}
}class LoginController {@ValidateTokenDecoratorpublic login(username: string, password: string) {localStorage.setItem('token', `token-${username}-${password}`);alert(`登录成功!\n用户名:${username}\n密码:${password}`);}public logout() {localStorage.clear();alert('退出成功!');}
}const loginController = new LoginController();const loginBtn = document.createElement('button');
loginBtn.innerText = "登录";
loginBtn.onclick = () => loginController.login('Lee', '123456');
document.body.append(loginBtn);const logoutBtn = document.createElement('button');
logoutBtn.innerText = "退出";
logoutBtn.onclick = () => loginController.logout();
document.body.append(logoutBtn);
数据请求
服务端
const http = require('http');
const url = require('url');http.createServer((req, res) => {res.writeHead(200, { "Access-Control-Allow-Origin": "*" });if (req.method === 'GET') {const { query } = url.parse(req.url, true);const result = { code: 200, msg: 'success', data: query };res.end(JSON.stringify(result));} else {const result = { code: 500, msg: 'fail', data: null };res.end(JSON.stringify(result));}}).listen(8888);console.log('Server running at http://127.0.0.1:8888/');
客户端
interface RequestParams {[prop: string]: any
}interface RequestResponse {code: number;msg: string;data: any;
}enum RequestMethod {GET = "GET",POST = "POST",DELETE = "DELETE",PUT = "PUT",
}const RequestDecorator = (method: RequestMethod, url: string): MethodDecorator => {return (target: Object, propertyKey: string | symbol, descriptor: PropertyDescriptor) => {const original = descriptor.value;descriptor.value = (params: RequestParams): Promise<RequestResponse | RequestParams> => {return new Promise((resolve: (value: any) => void, reject: (reason?: any) => void) => {url += `?`;for (const key in params) { url += `${key}=${params[key]}&`; }const xhr: XMLHttpRequest = new XMLHttpRequest();xhr.open(method, url);xhr.send();xhr.onreadystatechange = () => {if (xhr.readyState === 4) {if (xhr.status === 200) {const result: RequestResponse = JSON.parse(xhr.response);result.code === 200 ? resolve(result) : reject(result);} else {original(params);}}}})}}
}class RequestController {@RequestDecorator(RequestMethod.GET, "http://127.0.0.1:8888/")public request_01(params: RequestParams): Promise<RequestResponse | RequestParams> { return Promise.reject(params); }@RequestDecorator(RequestMethod.POST, "http://127.0.0.1:8888/")public request_02(params: RequestParams): Promise<RequestResponse | RequestParams> { return Promise.reject(params); }@RequestDecorator(RequestMethod.POST, "http://127.0.0.1:1000/")public request_03(params: RequestParams): Promise<RequestResponse | RequestParams> { return Promise.reject(params); }}const requestController = new RequestController();const requestBtn01 = document.createElement('button');
requestBtn01.innerText = "请求 01";
requestBtn01.onclick = async () => {const res = await requestController.request_01({ username: 'Lee', password: '123456' });// {"code":200,"msg":"success","data":{"username":"Lee","password":"123456"}}console.log(res);
};const requestBtn02 = document.createElement('button');
requestBtn02.innerText = "请求 02";
requestBtn02.onclick = async () => {const res = await requestController.request_02({ username: 'Lee', password: '123456' });// Uncaught (in promise) {code: 500, msg: 'fail', data: null}console.log(res);
};const requestBtn03 = document.createElement('button');
requestBtn03.innerText = "请求 03";
requestBtn03.onclick = async () => {const res = await requestController.request_03({ username: 'Lee', password: '123456' });// POST http://127.0.0.1:1000/?username=Lee&password=123456& net::ERR_CONNECTION_REFUSED// Uncaught (in promise) {username: 'Lee', password: '123456'}console.log(res);
};document.body.append(requestBtn01);
document.body.append(document.createElement('hr'));
document.body.append(requestBtn02);
document.body.append(document.createElement('hr'));
document.body.append(requestBtn03);
属性装饰器
大小写转换
const PropDecorator: PropertyDecorator = (target: Object, propertyKey: string | symbol) => {console.log('静态成员的类的构造函数 / 实例成员的类的原型', target);console.log('成员的名称', propertyKey);// 静态属性转大写if (typeof target === 'function') {(<any>target)[propertyKey] = (<any>target)[propertyKey].toUpperCase();} else { // 一般属性转小写let value: string;Object.defineProperty(target, propertyKey, {get: () => value.toLowerCase(),set: (v: any) => value = v,});}
}class Person {/*** @PropDecorator* 静态成员的类的构造函数 / 实例成员的类的原型 {constructor: ƒ}* 成员的名称 firstName*/@PropDecoratorpublic firstName: string = "Prosper";/*** @PropDecorator* 静态成员的类的构造函数 / 实例成员的类的原型 [class Person] { lastName: 'Lee' }* 成员的名称 lastName*/@PropDecoratorpublic static lastName: string = "Lee";}const person: Person = new Person();
console.log(`${person.firstName}${Person.lastName}!!!`); // prosperLEE!!!
元数据
安装依赖
$ npm i reflect-metadata
基础用法
import 'reflect-metadata';let person = { name: 'Lee' };// 描述name属性的基础信息
Reflect.defineMetadata('info', { key: 'string', value: 'string', desc: '这是一个名字字段!' }, person, 'name');// 打印name字段的基础信息
const info = Reflect.getMetadata('info', person, 'name');
console.log(info); // { key: 'string', value: 'string', desc: '这是一个名字字段!' }
参数装饰器
验证参数是否为必填项
import 'reflect-metadata';interface ErrorParam {parameterIndex: number;message: string;
}enum Sex {Empty = '',Sir = '先生',Madam = '女士',
}/*** 传参验证方法装饰器* @param target 静态成员的类的构造函数 / 实例成员的类的原型* @param propertyKey 成员的名称* @param descriptor 成员的属性描述符*/
const ValidateDecorator: MethodDecorator = (target: Object, propertyKey: string | symbol, descriptor: PropertyDescriptor) => {const original = descriptor.value;descriptor.value = (...args: any) => {const rules: ErrorParam[] = Reflect.getMetadata('rules', target, propertyKey) || [];rules.forEach((rule: ErrorParam) => {if (args[rule.parameterIndex] === undefined) {throw new Error(rule.message);}})original.apply(target, args);}
}/*** 参数装饰器工厂* @param field 字段名* @returns 参数装饰器*/
const RequiredDecorator = (field: string): ParameterDecorator => {return (target: Object, propertyKey: string | symbol | undefined, parameterIndex: number) => {if (propertyKey) {let rules: ErrorParam[] = [{ parameterIndex, message: `缺少参数: '${field}'` },...(Reflect.getMetadata('rules', target, propertyKey) || []),];Reflect.defineMetadata('rules', rules, target, propertyKey);}}
}class Person {@ValidateDecoratorpublic message(@RequiredDecorator('敬语')honorific: string,@RequiredDecorator('姓名')name: string,sex: Sex) {console.log(`${honorific} ${name} ${sex ? sex : Sex.Empty}!!!`);}}const person = new Person();person.message(); // Error: 缺少参数: '敬语'
person.message('尊敬的'); // Error: 缺少参数: '姓名'
person.message('尊敬的', 'Lee'); // 尊敬的 Lee !!!
person.message('尊敬的', 'Lee', Sex.Sir); // 尊敬的 Lee 先生!!!