Angular装饰器——Decorators

选中文字可对指定文章内容进行评论啦,绿色背景文字可以点击查看评论额。

最近有空学习了一下angular的基础知识,对于angular的装饰器有了一些理解。其实装饰器并非angular特有的,它是Typescript的语言特性。首先我们看看

什么是装饰器 

 

我们先看看Typescript官方的说明:

装饰器是一种特殊类型的声明,它能够被附加到类声明,方法, 访问符,属性或参数上。 装饰器使用 @expression这种形式,expression求值后必须为一个函数,它会在运行时被调用,被装饰的声明信息做为参数传入。

这里我们举一个例子:

@Input() name:string = '装饰器';

 

这里的Input就是装饰器,我们定义了一个变量name,类型为string,初始值为'装饰器',如果没有Input的话,这段代码只能给出这些信息,但是因为有了装饰器,所以,我们还知道了这个变量是angular组件的一个输入值,好的,Input装饰增强了这个变量的功能和作用,起到了装饰的作用。理解装饰,只要明白哪些是基础功能,哪些是装饰功能,那么使用起来就没有问题啦。是不是理解啦,再多说一点,为啥会有装饰,本质上它其实就是设计模式中装饰者模式的实现。

装饰者模式:

指在不必 改变原类文件 或者 使用继承 的情况下,动态扩展对象的功能

 

装饰器的分类

类装饰器:(target) => {}

方法装饰器:(target, methodName: string, descriptor: PropertyDescriptor) => {}

属性装饰器:   (target, propertyName: string) => {}

参数装饰器:  (target, methodName: string, paramIndex: number) => {}

 

 

为什么要使用装饰器?

其实为什么要使用装饰器,也不难理解,肯定是想增强某些功能呗,并且这些增强的功能还有一般性

我们还是举个例子吧,这个例子是网上看到的,参考链接如下:

https://www.jianshu.com/p/56f2c08ec591

基本的业务情况是:

我们需要控制一个组件loading的状态,那么设isLoading为true或者false,来控制开关。

// Child Component
// html
  <div *ngIf="isLoading">isLoading 为 true</div>
  <div *ngIf="!isLoading">isLoading 为 false</div>

//ts
  @Input() isLoading = false;
// Parent Component 
<app-child [isLoading]="'false'"></app-child>

调用的时候,本意是传递一个false参数,实际上传递的是一个false字符串。所以最终显示的是

isLoading 为 true

coerceBooleanProperty:是@angular/cdk/coercion中的一个方法,主要用途将传入的值进行强制转化为boolean.

为了解决这个问题,我们需要对传递进来的值进行格式化

@Input()
get isLoading() {
  return this._isLoading;
}
set isLoading(v) {
  this._isLoading = coerceBooleanProperty(v);
}
_isLoading = false;

添加上述代码后,最终能正确显示啦

isLoading 为 false

问题解决了是好事情,但是我们也不能仅仅停留在这里,而应该更进一步思考,除了这个方案外,还有没有更好的方案,如果在其他地方也遇到类似情况,那么是不是都要写set和get方法呢?从代码的可维护性上来说,此方案能解决问题,但不是最好的方案,我们看看下面的方案。

 

如何使用装饰器?

我们在这里使用装饰器来解决前面遇到的问题,从问题可以看出,我们应该写一个属性装饰器:

function propDecoratorFactory<T, D>(name: string, fallback: (v: T) => D): (target: any, propName: string) => void {
  function propDecorator(target: any, propName: string, originalDescriptor?: TypedPropertyDescriptor<any>): any {

    // 创建私有名字
    const privatePropName = `$$__${propName}`;

    // 首先判断是否设置过该值,防止重复操作
    if (Object.prototype.hasOwnProperty.call(target, privatePropName)) {
      console.warn(`The prop "${privatePropName}" is already exist, it will be overrided by ${name} decorator.`);
    }

    // 利用Object.defineProperty创建监听
    Object.defineProperty(target, privatePropName, {
      configurable: true,
      writable: true
    });

    // 触发情况:访问时会触发getter,设值时会触发setter
    // 具体不解释,可以看es6对Object.defineProperty的解释
    // 题外话,可以一同可看proxy,proxy对Object.defineProperty进行了优化。
    return {
      get(): string {
        // get 雷同于Setter解释
        return originalDescriptor && originalDescriptor.get
          ? originalDescriptor.get.bind(this)()
          : this[privatePropName];
      },
      set(value: T): void {
        // 首先判断修饰器下是否存在Setter,setter存在于originalDescriptor中,我们将先进行规定的format
        // 然后将format值传入其中,调用set 方法继续按照开发者的要求执行
        // 所以如下一个使用的执行顺序是:
        // @Input @InputBoolean set value(v){ this._v = v }
        //  1.调用Object.defineProperty对target上的$$__value进行监听,创建setter + getter
        //  2.setter(内) 触发 ,对传入的值进行格式化(即:调用传入的fallback(v)),拿到格式化的值后format_v
        //  3.调用originalDescriptor 中的setter(外:即开发者创建)方法,传入格式化的值format_v进行做后续处理
        // 因此此处只会对传入的值进行格式化,不会做任何操作。
        if (originalDescriptor && originalDescriptor.set) {
          originalDescriptor.set.bind(this)(fallback(value));
        }
        this[privatePropName] = fallback(value);
      }
    };
  }
  return propDecorator;
}

定义装饰器入口和格式化函数

// 装饰器入口
export function InputBoolean(): any {
  return propDecoratorFactory('InputBoolean', toBoolean);
}

// 格式化boolean函数
export function toBoolean(value: boolean | string): boolean {
  return coerceBooleanProperty(value);
}

使用装饰器:

@Input() @InputBoolean() isLoading = false;

看到没,不用再写get和set方法,而且这个组件是可扩展的,可以用在任何boolean变量上。

我们可以分析一下这个函数propDecoratorFactory 这个函数的定义如下


// 因为我们需要一个通用的装饰器工厂函数,所以我们传入两个参数name和fallback
// name: 装饰器名字(如:InputBoolean)
// fallback:调用需要执行的函数(如:toBoolean)

// 其次因为装饰器分为5种,但是我们主要针对属性和方法装饰器,因此
// originalDescriptor可为空(属性装饰器不包括该值,可看前面装饰器的介绍)

function propDecoratorFactory(name:string,fallback:function){
 
  return propDecorator(
    target: any, 
    propName: string, 
    originalDescriptor?: TypedPropertyDescriptor<any>
  ){
      ... 
  }
}

此函数有两个参数,返回值是一个函数,而返回的函数又是由propDecorator定义的。这个函数实际上是为装饰的属性增强了get和set方法。

至于为什么会增强,为什么使用了@InputBoolean() 后就增强了属性的方法,我们以后分析,现在知道怎么用就可以啦。

 

 

版权声明:著作权归作者所有。

相关推荐

Angular CLI在Angular项目里使用scss

Angular CLI支持多种css预处理,包括:cssscsslesssassstyl (stylus)默认值为css。Angular有两种方式指定css预处理。下面以scss为例:新建项目指定css预处理在新建angular项目是使用--style指定css预处理ng new my-project --style=scss 已有项目指定css预

Angular CLI:集成Angular Flex Layout

Angular CLI创建项目ng new myproject 安装Angular Flex Layoutnpm install @angular/flex-layout --save 导入Angular Flex Layout NgModule模块src/app/app.module.t

Angular CLI:集成Autoprefixer兼容多浏览器以及浏览器版本

Angular CLI使用Autoprefixer来兼容不同的浏览器以及多个浏览器版本,我们不需要做额外的工作来集成Autoprefixer。Autoprefixer内部依赖于Browserslist,根据Browserslist的配置决定兼容哪些浏览器或浏览器版本。如果我们对浏览器的兼容范围有自己的需求,可以设置Browserslist的配置。集成Browserslist配置到Angu

Angular 4:集成Angular Material2

Angular Material目的是按照Material Design的规范,使用Angular和TypeScript构建一个高质量的UI组件集。Angular 4已有的项目可以按照下面的步骤集成使用Angular Material2。安装Angular Material以及Angular CDKnpm install&nbs

Angular 5升级到Angular 6

Angular升级建议先到https://update.angular.io,它会根据你选择的原Angular和目标Angular版本给出一些升级建议。Angular 5升级到Angular 6升级前1、如果项目中有用到HttpModule和Http Service,把它们切换到HttpClientModule和HttpClient Service。HttpClient有几个特性:不需要调用.js

CKEditor5 Observable——装饰方法

上一节我们学习了在CK5中,如何绑定多个属性以及绑定多个Observable对象,今天我们学习如何装饰方法。 首先,我们提出一个问题,为什么会有装饰方法呢?以及什么叫做装饰?所谓装饰,就是在不改变原来方法功能的前提下,增加方法的功能,众所周知在java的IO流中,就有很多地方用到了装饰。 而在CK5中,装饰是什么意思呢?请看下面这段话:Decorating object met

多类选择器和后代选择器

今天看到一个css的样式代码是这样的ck.ck-reset,.ck.ck-reset_all,.ck.ck-reset_all * { box-sizing: border-box; width: auto; height: auto; position: static }瞬间石化了,以前写过这么多得选择器,还没有遇到过这样的情况。网上搜索了一下:两个选择器之间没有空格就是多类选择