TypeScript 学习笔记10: Decorators

原文链接: https://leanpub.com/essentialtypescript/read#decorators

1. Function Decorator

到 TodoService.ts 中,修改add()方法,在函数开头和结尾处打印log:

add(input): Todo {
    console.log(`add(${JSON.stringify(input)})`);
    // ...
    // 函数功能性代码
    // ...
    console.log(`add(${JSON.stringify(input)}) => ${JSON.stringify(todo)}`);

    return todo;
};

如果我们要让其它的函数也打印log,是不是要去修改每一个函数?使用Decorator,可以把log功能独立出来,不必把log代码嵌入函数体。
首先,假设我们有个Function decorator叫做 log,用它来修饰 add() 方法:

    @log
    add(input) {
        // ...
    }

然后,定义这个log decorator,它是一个function:

function log(target: Object, methodName: string, descriptor: TypedPropertyDescriptor<Function>) {
    var originalMethod = descriptor.value;
    descriptor.value = function(...args) {
        console.log(`${methodName}(${JSON.stringify(args)})`);
        let returnValue = originalMethod.apply(this, args);
        console.log(`${methodName}(${JSON.stringify(args)}) => ${JSON.stringify(returnValue)}`);
        return returnValue;
    }
}

这个函数的signature是固定的,所谓的 Function decorator必须带有这三个参数,这些参数提供了要修饰的function信息:
1. target 是function所在的对象(即TodoService的实例);
2. methodName 是函数名(即 add);
3. descriptor 包含了要修饰的函数的所有metadata,其中,descriptor.value 是函数体。
这个log decorator可以用来修饰任何函数。

2. Class Decorators

1. 实现一个class decorator

Class Decorator 和 Function Decorator 的用法类似:

//Validators.ts
@validatable
export class ValidatableTodo implements Todo {
    id: number;
    name: string;
    state: TodoState;
}

export function validatable(target: Function) {
    target.prototype.validate =  function() {
        let validators: IValidator[] = this._validators ? [].concat(this._validators) : [];
        let errors: IValidationResult[] = [];
        for(let validator of validators) {
            let result = validator(this);
            if(!result.isValid) {
                errors.push(result);
            }
        }
        return errors;
    };
}

export interface IValidationResult {
    isValid: boolean;
    message: string;
    property?: string;
}

export interface IValidator {
    (instance: Object): IValidationResult;
}

说明:
1. 在 TypeScriptTodo 工程中,创建Validators.ts文件;
2. 定义一个 ValidatableTodo 类,以前好像只定义了Todo 接口,还没有定义过相关的类。这个类带有“验证”功能,我们希望验证Todo的属性是否合法;
3. “验证” 功能本身不在class内部实现,而是作为一个decorator,可以让任何类具有“验证” 功能;
4. function validatable(target: Function) 就是一个class decorator,它的参数列表必须是这样;
5. 后面两个interface起辅助作用,并不是定义decorator必需的。

2. 再添加两个interface
export interface ValidatableTodo extends IValidatable {
}

export interface IValidatable {
    validate(): IValidationResult[];
}

这两个interface也不是class decorator必需的,只是为了提供类型信息。

3. 使用validate( )
//TodoService.ts
import { ValidatableTodo } from './Validators';

export default class TodoService implements ITodoService {
    // ...

    add(input) {

        let todo = new ValidatableTodo();
        todo.id = generateTodoId();
        todo.state = TodoState.Active;

        if(typeof input === 'string') {
            todo.name = input;
        } 
        else if(typeof input.name === 'string') {
            todo.name = input.name;
        } else {
            throw 'Invalid Todo name!';
        }

        let errors = todo.validate();
        if(errors.length) {
            let combinedErrors = errors.map(x => `${x.property}: ${x.message}`);
            throw `Invalid Todo! ${combinedErrors}`;
        }

        this.todos.push(todo);

        return todo;
    }

    //...
}

说明:
1. 第9行,使用 ValidatableTodo;
2. 第22行,验证。
现在,ValidatableTodo 的 _validators 还是空的,不会做任何验证,下一节开始添加。

3. Property Decorators

给 name 属性添加一个decorator: required

@validatable
export class ValidatableTodo implements Todo {
    id: number;

    @required
    name: string;

    state: TodoState;
}

function required(target: Object, propertyName: string) {
    let validatable = <{_validators: IValidator[]}>target;
    let validators = (validatable._validators || (validatable._validators = []));
    validators.push(function(instance){
        let propertyValue = instance[propertyName];
        let isValid = propertyValue != undefined;

        if(typeof propertyValue === 'string') {
            isValid = propertyValue && propertyValue.length > 0;
        }

        return {
            isValid,
            message: `${propertyName} is required`,
            property: propertyName
        };
    });
}

这就是 property decorator。
在Chrome的console 中运行如下代码:

System.import('Validators').then(function(module) {window.ValidatableTodo = module.ValidatableTodo});
var todo = new ValidatableTodo();
todo.validate();

注意,System.import 使用promise,是异步运行的,所以,等第一段代码运行完,再执行第二段。
看结果:
这里写图片描述
由于我们新创建的todo的name为空,validate() 会返回一个错误。
执行如下代码,再看看validate() 结果:

todo.name = "Rinse my hair";
todo.validate();

4. Decorator Factories

decorator 的参数列表是固定,如上面的 log(), valitable(), required() ,它们所带的参数都是“待修饰对象” 的相关属性。如果我们希望用一个正则表达式来验证 todo.name,怎么把这个正则表达式传送进去呢?使用decorator factory。
顾名思义,decorator factory 是一个函数,它返回一个decorator。

@validatable
export class ValidatableTodo implements Todo {
    id: number;

    @required
    @regex(`^[a-zA-Z ]*$`)
    name: string;

    state: TodoState;
}

function regex(pattern: string, flags?: string) {
    let expression = new RegExp(pattern, flags);
    return function(target: Object, propertyName: string) {
        let validatable = <{_validators: IValidator[]}>target;
        let validators = (validatable._validators || (validatable._validators = []));
        validators.push(function(instance){
            let propertyValue = instance[propertyName];
            let isValid = expression.test(propertyValue);
            return {
                isValid,
                message: `${propertyName} does not match ${expression}`,
                property: propertyName
            }
        });
    };
}

说明:
1. 第12行,定义了一个regex() 函数,它是decorator factory;
2. 第14行,返回一个property decorator,注意看它的参数列表,符合property decorator的定义;
3. 第6行,用regex修饰name;
4. property可以有多个decorator,function和class也不例外。
到Chrome的console中试一下:
这里写图片描述
说明:
1. 第一次,todo.name = “Rinse my hair!” ,字符串中有个感叹号,不符合regex;
2. 第二次,去掉感叹号,validate() 返回的errors为空。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值