启用装饰器支持

修改 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);

登录状态验证

TS(五):装饰器-LMLPHP

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);

数据请求

TS(五):装饰器-LMLPHP

服务端

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 先生!!!
10-13 13:42