在TypeScript和Angular中使用Getter和Setter

getter 和 setter 是一种众所周知的模式,在不同的语言中使用。在本文中,我想通过 TypeScript 中的示例帮助您理解 getter 和 setter。看看它如何在 Angular 应用程序中发挥作用。

首先简单介绍一下 TypeScript。

1. TypeScript 中的 Getter 和 Setter

假设我们有一个简单的产品类,其属性为:价格、名称、序列号。

1
2
3
4
5
export class Product {
private price: number;
public name: string;
public serialNumber: string;
}

这里我们有一个私有属性 price 和两个公共属性 name 和 serialNumber(序列号)。我们决定创建一个新的产品类实例——汽车。正如您在下图中看到的,我们可以访问 Product 类的任何公共属性,但不能访问私有属性。Price 属性只能从 Product 类访问,而不能从外部访问。

1
2
3
4
5
6
7
8
9
export class Product {
private price: number;
public name: string;
public serialNumber: string;
}

let car = new Product();
car.name = "volvo";
car.price = 123; //这里会报编译错误

为了解决这个问题,我们可以使用 getter 和 setter。Getter 和 Setter 是几乎所有语言中常用的模式。它们可以帮助您向属性添加额外的逻辑,正如我们将在上面看到的那样。

因此,让我们将价格属性设为私有,并为其添加 getter 和 setter。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
export class Product {
private _price: number;
public name: string;
public serialNumber: string;

public get price() {
return this._price;
}

public set price(value: number) {
this._price = value;
}
}

let car = new Product();
car.name = "volvo";
car.price = 123; //编译错误消失

语法很简单,对吧?现在我们调用的不是私有属性,而是同名的 setter。并且我们可以通过 getter 获取私有属性的值。通过以上修改,我们可以汽车实例中访问价格属性了。

除此之外,现在我们可以向 set 方法添加额外的逻辑。例如,在给价格赋值之前, 检查检查价格是否合理以及许多更复杂的逻辑。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
export class Product {
private _price: number;
public name: string;
public serialNumber: string;

public get price() {
return this._price;
}

public set price(value: number) {
if (value == 0) {
throw new Error("Price can not be zero");
}
this._price = value;
}
}

let car = new Product();
car.name = "volvo";
car.price = 123; //编译错误消失

2. Angular 中的 Getter 和 Setter

在 Angular 中,getter 和 setter 经常被用作拦截输入属性更改的方法之一。例如,我们有两个组件。products 作为父组件,product 作为子组件,具有两个输入(价格、名称)。

1
2
<app-product name="Soda" price="100"> </app-product>
<app-product name="Bread" price="10"> </app-product>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
export class ProductComponent {
private _price: number;
private _name: string;

@Input()
public get price() {
return this._price;
}

public set price(value: number) {
this._price = value;
}

@Input()
public get name() {
return this._name;
}

public set name(value: number) {
this._name = value;
}
}

因此,每当我们有一个输入值时,setter 就会被触发,正如我们上面所做的那样,我们可以向我们的 setter 添加一些额外的逻辑。但是在输 setter 使用额外逻辑有一些限制:

  1. 不要在 setter 中使用 side effects。

  2. 不要在 setter 中使用订阅。

  3. 不要使用_elementRef.detectChanges()。如果确实有必要,例如您在 setter 中调用一个方法来切换 ngStyle 属性中使用的某些属性或使用类似的 css 变量,那么最好选择_elementRef.markForCheck()。Angular 不接受输入属性 detectChanges,可能会导致控制台错误和性能问题

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    @Component(
    {
    selector: 'app-product-component',
    changeDetection: ChangeDetectionStrategy.OnPush
    }
    )
    export class ProductComponent {

    private _borderSize: number;

    constructor(private _elementRef: ElementRef) {

    }

    @Input()
    public get borderSize(): number {
    return this._borderSize;
    }

    public set borderSize(value: number) {
    this._borderSize = value;
    this.setStyles(value)
    }

    private setStyles(borderSize: number) {
    this._elementRef.nativeElement.style.setProperty(
    '--offset',
    '${borderSize + 'px'}'
    )

    this._elementRef.markForCheck();
    }

    }
  4. 如果您将 non-primitive 对象向下传递给子组件时,并在子组件中更改该对象的某个属性,则通常也会出现变更不生效的情况。因为父组件保持的对该对象的引用而不是值本身, 虽然对象的属性改变了, 引用并没有变, 所有父组件侦察不到变化。如果您使用 OnPush 更改检测策略来配置组件,您可能会发现由于底层对象引用没有修改,因此不会触发值变更检测。您经常会遇到这种情况, 尤其使用硬编码时,这些值不是来自异步流(例如 Observable)。

    如果您使用 Angular 的默认更改检测机制,则允许直接对象变更,因此将检测到在不提供新引用的情况下对对象进行的更改。

    但是,如果您不应用默认的更改检测 - 只需使用 Observable 和异步管道就是您的方式!

3. Angular 系列文章

最新更新以及更多 Angular 相关文章请访问 Angular 合集 | 鹏叔的技术博客

4. 总结

在这里,我尝试描述 TypeScript 中 getter 和 setter 的常见原理,以及在 Angular 应用程序中使用它们的一些有用技巧。希望这篇文章对您有所帮助,

5. 参考文档

Using Getters and Setters in TypeScript and Angular