装饰器
启用装饰器支持
修改 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: ƒ}
*/
@FuncDecorator
public static funcA(a1: string, a2: number) {
console.log(a1, a2);
}
/**
* @FuncDecorator
* 静态成员的类的构造函数 / 实例成员的类的原型 {funcB: ƒ, constructor: ƒ}
* 成员的名称 funcB
* 成员的属性描述符 {writable: true, enumerable: true, configurable: true, value: ƒ}
*/
@FuncDecorator
public 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 {
@ValidateTokenDecorator
public 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
*/
@PropDecorator
public firstName: string = "Prosper";
/**
* @PropDecorator
* 静态成员的类的构造函数 / 实例成员的类的原型 [class Person] { lastName: 'Lee' }
* 成员的名称 lastName
*/
@PropDecorator
public 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 {
@ValidateDecorator
public 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 先生!!!