# TypeScript 进阶

  • 类型断言
  • 类型保护
  • 类型推断

# 类型断言

类型断言(Type Assertion)可以用来手动指定一个值的类型。

# 语法

// 两种写法,在 tsx 语法(React 的 jsx 语法的 ts 版)中必须使用前者,即 值 as 类型。as 类型

<类型>

TypeScript 允许你覆盖它的推断,并且能以你任何你想要的方式分析它,这种机制被称为「类型断言」。TypeScript 类型断言用来告诉编译器你比它更了解这个类型,并且它不应该再发出错误。

const foo = {};
foo.bar = 123; // Error: 'bar' 属性不存在于 ‘{}’
foo.bas = 'hello'; // Error: 'bas' 属性不存在于 '{}'

这里的代码发出了错误警告,因为 foo 的类型推断为 {},即没有属性的对象。因此,你不能在它的属性上添加 bar 或 bas,你可以通过类型断言来避免此问题:你可以通过类型断言来避免此问题:

interface Foo {
  bar: number;
  bas: string;
}

const foo = {} as Foo;
foo.bar = 123;
foo.bas = 'hello';

# 类型断言与类型转换

它之所以不被称为「类型转换」,是因为转换通常意味着某种运行时的支持。但是,类型断言纯粹是一个编译时语法,同时,它也是一种为编译器提供关于如何分析代码的方法。

# 类型断言可能是有害的

如果你没有按约定添加属性,TypeScript 编译器并不会对此发出错误警告

interface Foo {
  bar: number;
  bas: string;
}

const foo = {} as Foo;

# 双重断言

类型断言,尽管我们已经证明了它并不是那么安全,但它也还是有用武之地。如下一个非常实用的例子所示,当使用者了解传入参数更具体的类型时,类型断言能按预期工作:

function handler(event: Event) {
  const mouseEvent = event as MouseEvent;
}

然而,如下例子中的代码将会报错,尽管使用者已经使用了类型断言:

function handler(event: Event) {
  const element = event as HTMLElement; // Error: 'Event' 和 'HTMLElement' 中的任何一个都不能赋值给另外一个
}

如果你仍然想使用那个类型,你可以使用双重断言。首先断言成兼容所有类型的 any,编译器将不会报错:

function handler(event: Event) {
  const element = (event as any) as HTMLElement; // ok
}

# 类型保护

TypeScript 熟知 JavaScript 中 instanceoftypeof 运算符的用法。如果你在一个条件块中使用这些,TypeScript 将会推导出在条件块中的的变量类型。如下例所示,TypeScript 将会辨别 string 上是否存在特定的函数,以及是否发生了拼写错误:

# typeof

function doSome(x: number | string) {
  if (typeof x === 'string') {
    // 在这个块中,TypeScript 知道 `x` 的类型必须是 `string`
    console.log(x.subtr(1)); // Error: 'subtr' 方法并没有存在于 `string` 上, 拼写错误
    console.log(x.substr(1)); // ok
  }

  x.substr(1); // Error: 无法保证 `x` 是 `string` 类型
}

# instanceof

class Foo {
  foo = 123;
  common = '123';
}

class Bar {
  bar = 123;
  common = '123';
}

function doStuff(arg: Foo | Bar) {
  if (arg instanceof Foo) {
    console.log(arg.foo); // ok
    console.log(arg.bar); // Error
  }
  if (arg instanceof Bar) {
    console.log(arg.foo); // Error
    console.log(arg.bar); // ok
  }
}

doStuff(new Foo());
doStuff(new Bar());

TypeScript 甚至能够理解 else。当你使用 if 来缩小类型时,TypeScript 知道在其他块中的类型并不是 if 中的类型:

class Foo {
  foo = 123;
}

class Bar {
  bar = 123;
}

function doStuff(arg: Foo | Bar) {
  if (arg instanceof Foo) {
    console.log(arg.foo); // ok
    console.log(arg.bar); // Error
  } else {
    // 这个块中,一定是 'Bar'
    console.log(arg.foo); // Error
    console.log(arg.bar); // ok
  }
}

doStuff(new Foo());
doStuff(new Bar());

# in

in 操作符可以安全的检查一个对象上是否存在一个属性,它通常也被作为类型保护使用:

interface A {
  x: number;
}

interface B {
  y: string;
}

function doStuff(q: A | B) {
  if ('x' in q) {
    // q: A
  } else {
    // q: B
    q.x = 1 // Error: 类型 B 上不存在属性 x
  }
}

# 自定义类型保护

// 仅仅是一个 interface
interface Foo {
  foo: number;
  common: string;
}

interface Bar {
  bar: number;
  common: string;
}

// 用户自己定义的类型保护!
function isFoo(arg: Foo | Bar): arg is Foo {
  return (arg as Foo).foo !== undefined;
}

// 用户自己定义的类型保护使用用例:
function doStuff(arg: Foo | Bar) {
  if (isFoo(arg)) {
    console.log(arg.foo); // ok
    console.log(arg.bar); // Error
  } else {
    console.log(arg.foo); // Error
    console.log(arg.bar); // ok
  }
}

doStuff({ foo: 123, common: '123' });
doStuff({ bar: 123, common: '123' });

# 类型推断

# 定义变量

let foo = 123; // foo 是 'number'
let bar = 'hello'; // bar 是 'string'

foo = bar; // Error: 不能将 'string' 赋值给 `number`

# 函数返回值

返回类型能被 return 语句推断,如下所示,推断函数返回为一个数字:

function add(a: number, b: number) {
  return a + b;
}

# 赋值

函数参数类型/返回值也能通过赋值来推断。如下所示,foo 的类型是 Adder,他能让 foo 的参数 abnumber 类型。

type Adder = (a: number, b: number) => number;
let foo: Adder = (a, b) => {
  a = 'hello'; // Error:不能把 'string' 类型赋值给 'number' 类型
  return a + b;
};

# 结构化

这些简单的规则也适用于结构化的存在(对象字面量),例如在下面这种情况下 foo 的类型被推断为 { a: number, b: number }

const foo = {
  a: 123,
  b: 456
};

foo.a = 'hello'; // Error:不能把 'string' 类型赋值给 'number' 类型
最后更新时间: 9/30/2021, 3:27:11 PM