TypeScript学习之日常类型
函数
函数是在 JavaScript 中传递数据的主要方式。 TypeScript 允许您指定函数的输入和输出值的类型。
参数类型注解
声明函数时,可以在每个参数后面加上类型注解,声明函数接受哪些类型的参数。 参数类型注释在参数名称之后:
1 | // Parameter type annotation |
当参数具有类型注释时,将检查该函数的参数:
1 | declare function greet(name: string): void; |
即使您的参数上没有类型注释,TypeScript 仍会检查您是否传递了正确数量的参数。
返回类型注解
您还可以添加返回类型注释。 返回类型注释出现在参数列表之后:
1 | function getFavoriteNumber(): number { |
与变量类型注解非常相似,您通常不需要返回类型注解,因为 TypeScript 会根据其 return
语句推断函数的返回类型。 上面例子中的类型注解并没有改变任何东西。 一些代码库将明确指定返回类型以用于文档目的,以防止意外更改,或仅出于个人喜好。
匿名函数
匿名函数与函数声明有点不同。 当一个函数出现在 TypeScript 可以确定如何调用它的地方时,该函数的参数会自动被赋予类型。
这是一个例子:
1 | // No type annotations here, but TypeScript can spot the bug |
即使参数 s
没有类型注释,TypeScript 还是使用 forEach
函数的类型以及推断的数组类型来确定 s
将具有的类型。
这个过程称为上下文类型,因为函数发生的上下文告知它应该具有什么类型。
与推理规则类似,您不需要明确了解这是如何发生的,但了解它确实发生可以帮助您注意到何时不需要类型注释。 稍后,我们将看到更多关于值出现的上下文如何影响其类型的示例。
对象类型
除了基本类型之外,您会遇到的最常见的类型是对象类型。 这指的是任何带有属性的 JavaScript 值,几乎是所有属性! 要定义对象类型,我们只需列出其属性及其类型。
例如,这是一个接受点状对象的函数:
1 | // The parameter's type annotation is an object type |
在这里,我们使用具有两个属性的类型注释参数 - x
和 y
- 这两个属性都是 number
类型。 您可以使用 ,
或 ;
来分隔属性,最后一个分隔符是可选的。
每个属性的类型部分也是可选的。 如果不指定类型,则假定为 any
。
可选属性
对象类型还可以指定它们的部分或全部属性是可选的。 为此,请在属性名称后添加 ?
:
1 | function printName(obj: { first: string; last?: string }) { |
在 JavaScript 中,如果您访问一个不存在的属性,您将获得值 undefined
而不是运行时错误。 因此,当您从可选属性中读取数据时,您必须在使用它之前检查 undefined
。
1 | function printName(obj: { first: string; last?: string }) { |
联合类型
TypeScript 的类型系统允许您使用各种运算符从现有类型中构建新类型。 现在我们知道如何编写几种类型,是时候开始以有趣的方式组合它们了。
定义联合类型
您可能会看到的第一种组合类型的方法是联合类型。 联合类型是由两种或多种其他类型组成的类型,表示可能是这些类型中的任何一种的值。 我们将这些类型中的每一种都称为联合的成员。
让我们编写一个可以对字符串或数字进行操作的函数:
1 | function printId(id: number | string) { |
使用联合类型
提供与联合类型匹配的值很容易 - 只需提供与联合的任何成员匹配的类型即可。 如果你有一个联合类型的值,你如何处理它?
TypeScript 只有在对联合的每个成员都有效的情况下才允许操作。 例如,如果您有联合 string | number
,则不能使用仅在 string
上可用的方法:
1 | function printId(id: number | string) { |
解决方案是用代码缩小联合,就像在没有类型注释的 JavaScript 中一样。 当 TypeScript 可以根据代码的结构为某个值推断出更具体的类型时,就会发生缩小。
例如,TypeScript 知道只有 string
值才会有 typeof
值 "string"
:
1 | function printId(id: number | string) { |
另一个例子是使用像 Array.isArray
这样的函数:
1 | function welcomePeople(x: string[] | string) { |
请注意,在 else
分支中,我们不需要做任何特别的事情——如果 x
不是 string[]
,那么它一定是 string
。
有时你会有一个联合,所有成员都有共同点。 例如,数组和字符串都有一个 slice
方法。 如果联合中的每个成员都有一个共同的属性,则可以使用该属性而不会缩小类型:
1 | // Return type is inferred as number[] | string |
类型的联合似乎具有这些类型的属性的交集,这可能会令人困惑。 这不是偶然的——联合这个名字来源于类型论。 联合
number | string
是通过取每种类型的值的联合组成的。 请注意,给定两个具有关于每个集合的相应事实的集合,只有这些事实的交集适用于集合本身的并集。 例如,如果我们有一个房间里有戴帽子的高个子,而另一个房间里有戴帽子的说西班牙语的人,在组合这些房间后,我们对每个人的唯一了解就是他们必须戴帽子。
实现一个 Swift 中的 compactMap 函数
1 | /** |
类型断言
有时你会得到关于 TypeScript 无法知道的值类型的信息。
例如,如果您使用的是 document.getElementById
,TypeScript 只知道这将返回某种 HTMLElement
,但您可能知道您的页面将始终具有具有给定 ID 的 HTMLCanvasElement
。
在这种情况下,您可以使用类型断言来指定更具体的类型:
1 | const myCanvas = document.getElementById("main_canvas") as HTMLCanvasElement; |
与类型注释一样,类型断言被编译器删除,不会影响代码的运行时行为。
您还可以使用尖括号语法(除非代码在 .tsx
文件中),它是等效的:
1 | const myCanvas = <HTMLCanvasElement>document.getElementById("main_canvas"); |
提醒:因为类型断言在编译时被删除,所以没有与类型断言关联的运行时检查。 如果类型断言错误,则不会产生异常或
null
。
TypeScript 只允许类型断言转换为更具体或更不具体的类型版本。 此规则可防止 “impossible” 强制,例如:
1 | const x = "hello" as number; |
有时,此规则可能过于保守,并且不允许可能有效的更复杂的强制转换。 如果发生这种情况,您可以使用两个断言,首先是 any
(或 unknown
,我们稍后会介绍),然后是所需的类型:
1 | declare const expr: any; |