mirror of
https://github.com/wangdoc/typescript-tutorial.git
synced 2025-12-20 00:50:17 +08:00
docs(class): modify useDefineForClassFields, fixed #113
This commit is contained in:
193
docs/class.md
193
docs/class.md
@@ -954,65 +954,6 @@ class Test extends getGreeterBase() {
|
||||
|
||||
上面示例中,例一和例二的`extends`关键字后面都是构造函数,例三的`extends`关键字后面是一个表达式,执行后得到的也是一个构造函数。
|
||||
|
||||
对于那些只设置了类型、没有初值的顶层属性,有一个细节需要注意。
|
||||
|
||||
```typescript
|
||||
interface Animal {
|
||||
animalStuff: any;
|
||||
}
|
||||
|
||||
interface Dog extends Animal {
|
||||
dogStuff: any;
|
||||
}
|
||||
|
||||
class AnimalHouse {
|
||||
resident: Animal;
|
||||
|
||||
constructor(animal:Animal) {
|
||||
this.resident = animal;
|
||||
}
|
||||
}
|
||||
|
||||
class DogHouse extends AnimalHouse {
|
||||
resident: Dog;
|
||||
|
||||
constructor(dog:Dog) {
|
||||
super(dog);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
上面示例中,类`DogHouse`的顶层成员`resident`只设置了类型(`Dog`),没有设置初值。这段代码在不同的编译设置下,编译结果不一样。
|
||||
|
||||
如果编译设置的`target`设成大于等于`ES2022`,或者`useDefineForClassFields`设成`true`,那么下面代码的执行结果是不一样的。
|
||||
|
||||
```typescript
|
||||
const dog = {
|
||||
animalStuff: 'animal',
|
||||
dogStuff: 'dog'
|
||||
};
|
||||
|
||||
const dogHouse = new DogHouse(dog);
|
||||
|
||||
console.log(dogHouse.resident) // undefined
|
||||
```
|
||||
|
||||
上面示例中,`DogHouse`实例的属性`resident`输出的是`undefined`,而不是预料的`dog`。原因在于 ES2022 标准的 Class Fields 部分,与早期的 TypeScript 实现不一致,导致子类的那些只设置类型、没有设置初值的顶层成员在基类中被赋值后,会在子类被重置为`undefined`,详细的解释参见《tsconfig.json》一章,以及官方 3.7 版本的[发布说明](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-7.html#the-usedefineforclassfields-flag-and-the-declare-property-modifier)。
|
||||
|
||||
解决方法就是使用`declare`命令,去声明顶层成员的类型,告诉 TypeScript 这些成员的赋值由基类实现。
|
||||
|
||||
```typescript
|
||||
class DogHouse extends AnimalHouse {
|
||||
declare resident: Dog;
|
||||
|
||||
constructor(dog:Dog) {
|
||||
super(dog);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
上面示例中,`resident`属性的类型声明前面用了`declare`命令,这样就能确保在编译目标大于等于`ES2022`时(或者打开`useDefineForClassFields`时),代码行为正确。
|
||||
|
||||
## 可访问性修饰符
|
||||
|
||||
类的内部成员的外部可访问性,由三个可访问性修饰符(access modifiers)控制:`public`、`private`和`protected`。
|
||||
@@ -1278,6 +1219,140 @@ class A {
|
||||
}
|
||||
```
|
||||
|
||||
## 顶层属性的处理方法
|
||||
|
||||
对于类的顶层属性,TypeScript 早期的处理方法,与后来的 ES2022 标准不一致。这会导致某些代码的运行结果不一样。
|
||||
|
||||
类的顶层属性在 TypeScript 里面,有两种写法。
|
||||
|
||||
```typescript
|
||||
class User {
|
||||
// 写法一
|
||||
age = 25;
|
||||
|
||||
// 写法二
|
||||
constructor(private currentYear: number) {}
|
||||
}
|
||||
```
|
||||
|
||||
上面示例中,写法一是直接声明一个实例属性`age`,并初始化;写法二是顶层属性的简写形式,直接将构造方法的参数`currentYear`声明为实例属性。
|
||||
|
||||
TypeScript 早期的处理方法是,先在顶层声明属性,但不进行初始化,等到运行构造方法时,再完成所有初始化。
|
||||
|
||||
```typescript
|
||||
class User {
|
||||
age = 25;
|
||||
}
|
||||
|
||||
// TypeScript 的早期处理方法
|
||||
class User {
|
||||
age: number;
|
||||
|
||||
constructor() {
|
||||
this.age = 25;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
上面示例中,TypeScript 早期会先声明顶层属性`age`,然后等到运行构造函数时,再将其初始化为`25`。
|
||||
|
||||
ES2022 标准里面的处理方法是,先进行顶层属性的初始化,再运行构造方法。这在某些情况下,会使得同一段代码在 TypeScript 和 JavaScript 下运行结果不一致。
|
||||
|
||||
这种不一致一般发生在两种情况。第一种情况是,顶层属性的初始化依赖于其他实例属性。
|
||||
|
||||
```typescript
|
||||
class User {
|
||||
age = this.currentYear - 1998;
|
||||
|
||||
constructor(private currentYear: number) {
|
||||
// 输出结果将不一致
|
||||
console.log('Current age:', this.age);
|
||||
}
|
||||
}
|
||||
|
||||
const user = new User(2023);
|
||||
```
|
||||
|
||||
上面示例中,顶层属性`age`的初始化值依赖于实例属性`this.currentYear`。按照 TypeScript 的处理方法,初始化是在构造方法里面完成的,会输出结果为`25`。但是,按照 ES2022 标准的处理方法,初始化在声明顶层属性时就会完成,这时`this.currentYear`还等于`undefined`,所以`age`的初始化结果为`NaN`,因此最后输出的也是`NaN`。
|
||||
|
||||
第二种情况与类的继承有关,子类声明的顶层属性在父类完成初始化。
|
||||
|
||||
```typescript
|
||||
interface Animal {
|
||||
animalStuff: any;
|
||||
}
|
||||
|
||||
interface Dog extends Animal {
|
||||
dogStuff: any;
|
||||
}
|
||||
|
||||
class AnimalHouse {
|
||||
resident: Animal;
|
||||
|
||||
constructor(animal:Animal) {
|
||||
this.resident = animal;
|
||||
}
|
||||
}
|
||||
|
||||
class DogHouse extends AnimalHouse {
|
||||
resident: Dog;
|
||||
|
||||
constructor(dog:Dog) {
|
||||
super(dog);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
上面示例中,类`DogHouse`继承自`AnimalHouse`。它声明了顶层属性`resident`,但是该属性的初始化是在父类`AnimalHouse`完成的。不同的设置运行下面的代码,结果将不一致。
|
||||
|
||||
```typescript
|
||||
const dog = {
|
||||
animalStuff: 'animal',
|
||||
dogStuff: 'dog'
|
||||
};
|
||||
|
||||
const dogHouse = new DogHouse(dog);
|
||||
|
||||
console.log(dogHouse.resident) // 输出结果将不一致
|
||||
```
|
||||
|
||||
上面示例中,TypeScript 的处理方法,会使得`resident`属性能够初始化,所以输出参数对象的值。但是,ES2022 标准的处理方法是,顶层属性的初始化先于构造方法的运行。这使得`resident`属性不会得到赋值,因此输出为`undefined`。
|
||||
|
||||
为了解决这个问题,同时保证以前代码的行为一致,TypeScript 从3.7版开始,引入了编译设置`useDefineForClassFields`。这个设置设为`true`,则采用 ES2022 标准的处理方法,否则采用 TypeScript 早期的处理方法。
|
||||
|
||||
它的默认值与`target`属性有关,如果输出目标设为`ES2022`或者更高,那么`useDefineForClassFields`的默认值为`true`,否则为`false`。关于这个设置的详细说明,参见官方 3.7 版本的[发布说明](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-7.html#the-usedefineforclassfields-flag-and-the-declare-property-modifier)。
|
||||
|
||||
如果希望避免这种不一致,让代码在不同设置下的行为都一样,那么可以将所有顶层属性的初始化,都放到构造方法里面。
|
||||
|
||||
```typescript
|
||||
class User {
|
||||
age: number;
|
||||
|
||||
constructor(private currentYear: number) {
|
||||
this.age = this.currentYear - 1998;
|
||||
console.log('Current age:', this.age);
|
||||
}
|
||||
}
|
||||
|
||||
const user = new User(2023);
|
||||
```
|
||||
|
||||
上面示例中,顶层属性`age`的初始化就放在构造方法里面,那么任何情况下,代码行为都是一致的。
|
||||
|
||||
对于类的继承,还有另一种解决方法,就是使用`declare`命令,去声明子类顶层属性的类型,告诉 TypeScript 这些属性的初始化由父类实现。
|
||||
|
||||
```typescript
|
||||
class DogHouse extends AnimalHouse {
|
||||
declare resident: Dog;
|
||||
|
||||
constructor(dog:Dog) {
|
||||
super(dog);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
上面示例中,`resident`属性的类型声明前面用了`declare`命令。这种情况下,这一行代码在编译成 JavaScript 后就不存在,那么也就不会有行为不一致,无论是否设置`useDefineForClassFields`,输出结果都是一样的。
|
||||
|
||||
## 静态成员
|
||||
|
||||
类的内部可以使用`static`关键字,定义静态成员。
|
||||
|
||||
@@ -819,6 +819,12 @@ class User {
|
||||
|
||||
如果`"types": []`,就表示不会自动将所有`@types`模块加入编译。
|
||||
|
||||
### useDefineForClassFields
|
||||
|
||||
`useDefineForClassFields`这个设置针对的是,在类(class)的顶部声明的属性。TypeScript 早先对这一类属性的处理方法,与写入 ES2022 标准的处理方法不一致。这个设置设为`true`,就用来开启 ES2022 的处理方法,设为`false`就是 TypeScript 原有的处理方法。
|
||||
|
||||
它的默认值跟`target`属性有关,如果编译目标是`ES2022`或更高,那么`useDefineForClassFields`默认值为`true`,否则为`false`。
|
||||
|
||||
### useUnknownInCatchVariables
|
||||
|
||||
`useUnknownInCatchVariables`设置`catch`语句捕获的`try`抛出的返回值类型,从`any`变成`unknown`。
|
||||
|
||||
Reference in New Issue
Block a user