原文:10 TypeScript features you might not be using yet or didn’t understand
image.png

10个你不了解或者还没使用的TS特性

1、怎么使用或者什么时候用unknown类型 (或者unknown v any);
2、索引访问类型(检查类型)
3、infer关键字
4、断言函数
5、什么时候使用never
6、const断言
7、override关键字
8、类中的static代码块
9、弃用支持
10、实例和静态索引签名

1、怎么和什么时候使用unknown类型(或者unknown v any)

unknown类型比any要更安全。
相同点:

  1. let anyValue: any = "any value";
  2. anyValue = true;
  3. anyValue = 123;
  4. console.log(anyValue);
  5. let unknownValue: unknown;
  6. unknownValue = "unknown value";
  7. unknownValue = true;
  8. unknownValue = 123;
  9. console.log(unknownValue);

any不太一样,unknown可以阻止我们用一些不知道的类型做一些事情。
下面的例子:

  1. const anyValue: any = "any value";
  2. console.log(anyValue.add());

或者这个:

  1. const anyValue: any = true;
  2. anyValue.typescript.will.not.complain.about.this.method();

上面的例子,在构建的时候不会有问题,但是在运行时就会报错。
当使用any类型的时候,TS编译器不会做任何的类型推断,所以,我们不能避免运行时的问题。

当使用unknown类型的时候,在构建时就会报错,IDE也会高亮错误。
下面示例:

  1. let unknownValue: unknown;
  2. unknownValue = "unknown value";
  3. unknownValue.toString(); // Error: Object is of type 'unknown'.
  4. unknownValue = 100;
  5. const value = unknownValue + 10; // Error: Object is of type 'unknown'.
  6. unknownValue = console;
  7. unknownValue.log("test"); // Error: Object is of type 'unknown'.

每一次我们使用unknown类型时,都会报错。

为了使unknown类型能很好的工作,需要类型向下兼容。

使用类型断言:

  1. let unknownValue: unknown;
  2. unknownValue = function () {};
  3. (unknownValue as Function).call(null);

使用类型守卫:

  1. let unknownValue: unknown;
  2. unknownValue = "unknown value";
  3. if (typeof unknownValue === "string") unknownValue.toString();

自定义类型守卫:

  1. let unknownValue: unknown;
  2. type User = { username: string };
  3. function isUser(maybeUser: any): maybeUser is User {
  4. return "username" in maybeUser;
  5. }
  6. unknownValue = { username: "John" };
  7. if (isUser(unknownValue))
  8. console.log(unknownValue.username);

使用断言函数:

  1. let unknownValue: unknown;
  2. unknownValue = 123;
  3. function isNumber(value: unknown): asserts value is number {
  4. if (typeof value !== "number") throw Error("Not a number");
  5. }
  6. isNumber(unknownValue);
  7. unknownValue.toFixed();

2、索引访问类型(检索类型)

  1. type User = {
  2. id: number;
  3. username: string;
  4. email: string;
  5. address: {
  6. city: string;
  7. state: string;
  8. country: string;
  9. postalCode: number;
  10. };
  11. addons: { name: string, id: number }[];
  12. };
  13. type id = User["id"]; // number
  14. type Session = User["address"];
  15. // {
  16. // city: string;
  17. // state: string;
  18. // country: string;
  19. // postalCode: string;
  20. // }
  21. type Street = User["address"]["state"]; // string
  22. type Addons = User["addons"][number]; // { name: string; id: number; }

上面的例子中,我们从已有的对象中,创建了新的类型,idSessionStreetAddons
可以直接在函数中使用:

  1. function printAddress(address: User["address"]): void {
  2. console.log(`
  3. city: ${address.city},
  4. state: ${address.state},
  5. country: ${address.country},
  6. postalCode: ${address.postalCode}
  7. `);
  8. }

3、infer关键字

infer关键字允许你在条件语句中,从另外一个类型中推断出另一个类型。

  1. const User = {
  2. id: 123,
  3. username: 'John',
  4. email: 'john@gmail.com',
  5. addons: [
  6. { name: 'WriteAddon', id: 1 },
  7. { name: 'TodoAddon', id: 2 }
  8. ]
  9. };
  10. type UnpackArray<T> = T extends (infer R)[] ? R: T;
  11. type AddonType = UnpackArray<typeof User.addons>; // { name: string, id: number }

上面的案例中,我们通过addons类型推断出了一个单独的类型。

4、断言函数

有一组特定的函数,如果有未预料的事情发生会throw抛出一个错误。他们被叫做断言函数。

  1. function assertIsString(value: unknown): asserts value is string {
  2. if (typeof value !== "string") throw Error("value is not a string");
  3. }

在上面的例子里,我们检查value值是不是string。在这个例子,我们什么都不做。不然,就会抛出一个错误。

这里有一个复杂的例子展示怎么断言对象和内部对象。

  1. type User = {
  2. id: number;
  3. username: string;
  4. email: string;
  5. address: {
  6. city: string;
  7. state: string;
  8. country: string;
  9. postalCode: number;
  10. };
  11. };
  12. function assertIsObject(obj: unknown, errorMessage: string = 'Invalid object'): asserts obj is object {
  13. if(typeof obj !== 'object' || obj === null) throw new Error(errorMessage);
  14. }
  15. function assertIsAddress(address: unknown): asserts address is User['address'] {
  16. const errorMessage = 'Invalid address';
  17. assertIsObject(address, errorMessage);
  18. if(
  19. !('city' in address) ||
  20. !('state' in address) ||
  21. !('country' in address) ||
  22. !('postalCode' in address)
  23. ) throw new Error(errorMessage);
  24. }
  25. function assertIsUser(user: unknown): asserts user is User {
  26. const errorMessage = 'Invalid user';
  27. assertIsObject(user, errorMessage);
  28. if(
  29. !('id' in user) ||
  30. !('username' in user) ||
  31. !('email' in user)
  32. ) throw new Error(errorMessage);
  33. assertIsAddress((user as User).address);
  34. }

调用函数:

  1. // Mock example
  2. class UserWebService {
  3. static getUser = (id: number): User | unknown => undefined;
  4. }
  5. const user = UserWebService.getUser(123);
  6. assertIsUser(user); // if user invlid an error will be thrown
  7. user.address.postalCode; // at this point we know that user is a valid object

5、什么时候使用never

never类型代表着一个值不会被观察到。

在一个返回类型中,这意味着函数会抛出异常或者终止程序的执行。

  1. function plus1If1(value: number): number | never {
  2. if(value === 1) return 1 + value;
  3. throw new Error('Error was thrown');
  4. }

下面是使用promise的案例:

  1. const promise = (value: number) => new Promise<number | never>((resolve, reject) => {
  2. if(value === 1) resolve(1 + value);
  3. reject(new Error('Error was thrown'));
  4. });

另外,永不停止的程序:

  1. function endlessWhile(): never {
  2. while(true) {
  3. /* ... */
  4. }
  5. }

never也经常出现在,TS判断出在联合类型已没有其他类型。

  1. function valueCheck(value: string | number) {
  2. if (typeof value === "string") {
  3. // value has type string
  4. } else if (typeof value === "number") {
  5. // value has type number
  6. } else {
  7. value; // value has type never
  8. }
  9. }

6、使用const断言

当我们用const断言构建一个字面量表达式时,我们可以给语言发信号。

  • 该表达式中的字面类型都不应该被扩展(例如不必为 ‘hello’ 字符串指定string类型)
  • 对象字面量有readonly属性
  • 数组字面量变成readonly元组

下面是const实际使用的案例:

  1. const email = 'john@gmail.com'; // type 'john@gmail.com'
  2. const phones = [8494922901, 1238399293]; // type number[]
  3. const session = { id: 123, name: 'x1keck323jKJdf1' };
  4. // type
  5. // {
  6. // id: number;
  7. // name: string;
  8. // }
  9. const username = 'John' as const; // type 'John'
  10. const roles = [ 'read', 'write'] as const; // type readonly ["read", "write"]
  11. const address = { street: 'Tokyo', country: 'Japan' } as const;
  12. // type
  13. // {
  14. // readonly street: "Tokyo";
  15. // readonly country: "Japan";
  16. // }
  17. const user = {
  18. email,
  19. phones,
  20. session,
  21. username,
  22. roles,
  23. address
  24. } as const;
  25. // {
  26. // readonly email: "john@gmail.com";
  27. // readonly phones: number[];
  28. // readonly session: {
  29. // id: number;
  30. // name: string;
  31. // };
  32. // readonly username: "John";
  33. // readonly roles: readonly ["read", "write"];
  34. // readonly address: {
  35. // readonly street: "Tokyo";
  36. // readonly country: "Japan";
  37. // };
  38. // }
  39. // With const assertion
  40. // user.email = 'jim@gmail.com' // Error
  41. // user.phones = []; // Error
  42. user.phones.push(859324293);
  43. // user.session = { name: 'new session name', id: 124 }; // Error
  44. user.session.name = 'new session name';
  45. // With const assertion + inner const assertion
  46. // user.username = 'Jim'; // Error
  47. // user.roles.push('ReadAndWrite'); // Error
  48. // user.address.city = 'Osaka'; // Error

另外,使用const断言,可以帮助我们把字符串数组转成字符串联合类型。

  1. const roles = ['read', 'write', 'readAndWrite'] as const;
  2. type Roles = typeof roles[number];
  3. // equals to this
  4. // type Roles = "read" | "write" | "readAndWrite"
  5. // or even like this
  6. type RolesInCapital = Capitalize<typeof roles[number]>;
  7. // equals to this
  8. // type RolesInCapital = "Read" | "Write" | "ReadAndWrite"

上面的例子,使用[number]roles数组后面,告诉TS编译器抓取roles数组里有编号的索引值。

7、override关键字

自从4.3版本开始,我们可以在子类中使用override关键字覆盖已存在的行为。

  1. class Employee {
  2. doWork() {
  3. console.log('I\'m working!');
  4. }
  5. }
  6. class Developer extends Employee {
  7. override doWork() {
  8. console.log('I\'m programming!')
  9. }
  10. }

我建议你修改你的配置把这个规则改成强制,只需要把noImplicitOverride改成true

  1. {
  2. "compilerOptions": {
  3. "noImplicitOverride": true
  4. }
  5. }

8、类中的static块。

从4.4版本开始,ts支持类中的static语法,你可以为你的静态成员写更复杂的初始化代码。

  1. function userCount() {
  2. /* Mock data */
  3. return 15;
  4. };
  5. class User {
  6. id = 0;
  7. static count: number = 0;
  8. constructor(
  9. public username: string,
  10. public age: number
  11. ) {
  12. this.id = ++User.count;
  13. }
  14. static {
  15. User.count += userCount();
  16. }
  17. }
  18. console.log(User.count); // 15
  19. new User('John', 23);
  20. new User('Suzi', 31);
  21. console.log(User.count); // 17

9、弃用的支持 Deprecation support

要标识一些东西是deprecated弃用的,可以使用JSDoc注释:

  1. /** @deprecated use User class instead */
  2. class OldUser {}
  3. class User {
  4. /** @deprecated */
  5. constructor(public username: string, public age: number) {}
  6. /** @deprecated */
  7. static createUser(username: string, age: number) {
  8. /* ... */
  9. }
  10. static create(username: string, age: number) {
  11. /* ... */
  12. }
  13. }
  14. const oldUser = new OldUser();
  15. const user1 = new User('John', 34);
  16. const user2 = User.createUser('John', 34);
  17. const user3 = User.create('John', 34);

IDE会识别出来,并且建议不使用已经废弃的部分。
image.png
他们都有删除线,建议你不应该使用。
下面也展示了弃用的原因。
image.png

10、实例和静态索引签名

静态索引签名可以为一个值设置更多的属性 除了显示声明的。

  1. class User {
  2. username: string;
  3. age: number;
  4. constructor(username: string,age: number) {
  5. this.username = username;
  6. this.age = age;
  7. }
  8. [propName: string]: string | number;
  9. }
  10. const user = new User('John', 23);
  11. user['phone'] = '+3434234898';
  12. const something = user['something'];

下面的例子显示了用法:

  1. class User {
  2. username: string;
  3. age: number;
  4. constructor(username: string,age: number) {
  5. this.username = username;
  6. this.age = age;
  7. }
  8. static [propName: string]: string | number;
  9. }
  10. User['userCount'] = 0;
  11. const something = User['something'];