教程中的难点
提示
本文整理自 TypeScript
官方文档中的 handlebook,版本为 V5.4
# TypeScript
基础
- 静态类型检查
Typescript
会检查类型是否符合Typescript
会检查参数值是否符合预期
function greet(person, date) {
console.log(`Hello ${person}, today is ${date}!`)
}
greet("Brendan") // 提示缺一个参数
1
2
3
4
5
2
3
4
5
- 类型注释永远不会改变程序运行时的行为
- 默认情况下,可以使用 any,并且可以将
null
和undefined
赋值给任何其他类型 - 可以使用
use strict
来改变Typescipt
检验类型的程度
# 常见的 TypeScript
类型
- 最原始的类型:
string
number
boolean
- 指定数组类型的方法
arr: number[]
arr: Array<number>
any
是特殊类型,如果不希望特定值导致类型检验错误可以使用any
TypeScript
允许制定Functions
的输入和输出值的类型- 指定返回
Promise
的函数
async function getFavoriteNumber(): Promise<number> {
return 26
}
1
2
3
2
3
- 在函数作为回调函数而匿名时,ts 会自动对其进行转义
- 对象类型
obj: { first: string, second: number }
- 在对象类型的属性后加入?表示可选属性
- 使用 | 可以连接两个符号
- 类型别名(type alias)
type Point = { x: number; y: number }
- type alias 只是其他类型别名,并不是创造了一种新的类型,可以理解为引用了某种类型
- 接口声明(interfaces)
- 也可以用以声明对象的属性,大部分情况下,interface 和 type 是可以混用的
- interface 和 type 的区别
- type alias 不可以重新打开加入新的属性,接口始终是可以扩展的
- 类型断言
- 可以使用类型断言来帮助 ts 理解对应的类型,但是如果判断错了,ts 将不会生成异常或者 null
- 可列举的类型
- 可以在类型中引入特定的字符串和数字
boolean
实际上只是联合了true
和false
的类型- 使用
as const
将会使一个对象被转化为一个可列举的对象类型
null
和undefined
- 开启
strictNullChecks
后不允许undefined
和null
赋值给任何类型 - 非空断言运算符,
x!.toFixed()
- 开启
bigInt
和symbol
不太常见
# 类型缩窄
- 通常,我们需要使用
if - else
来缩小类型的范围
funciton padLeft (padding: number | string, input: string): string {
if (typeof padding === 'number') {
return ' '.repeat(padding) + input
}
return padding + input
}
1
2
3
4
5
6
2
3
4
5
6
- 使用 typeof 来规避
null
或undefined
存在typeof null === 'object'
的问题,官方更加推荐使用!variable
来判断对应的变量是否为null
或者undefined
- Typescript 还会根据等号条件(==, !=, ===, !==)来进行类型范围的缩小
- TypeScript 会根据 in 关键字来缩小类型检查的范围
- in 关键字会检查某个属性是否出现在一个对象的原型链上
- instanceof 也可以帮助 TypeScript 缩小类型范围
- 在进行第一次赋值时,TypeScript 会对变量进行合适的类型缩小
- 在开头的代码中,TypeScript 实际上用到了控制流分析,第一个 if 后,padding 就只能是 string 了,所以可以在控制流的情况下限制了 padding 的类型
- 类型谓语
- 使用类型谓语可以进行类型守卫,常见用法如下
interface Bird {
fly: () => void
}
interface Fish {
swim: () => void
}
function isFish(pet: Fish | Bird): pet is Fish {
return (pet as Fish).swim !== undefined
}
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
在使用类型谓语时,实际上已经已经默认返回值为 boolean
类型了,所以不需要指定返回值的类型。
类型谓语的作用在于提前缩小参数的范围,从而使用更简单的逻辑来判断参数是否为某个类型。
在已经消除了所有可能性之后,ts 使用
never
表示剩余的类型可能性。场景题
/**
* @description 设想一个场景,要定义一个可能是圆形也可能是正方形的类型,如何书写类型以便于更好的求得面积
*/
function getArea(shape) {
switch (shape.kind) {
case "circle":
return Math.PI * shape.radius * shape.radius
case "square":
return shape.side * shape.side
default:
throw new Error("unexpected shape")
}
}
// 测试
var shape = {
kind: "circle",
radius: 10,
}
console.log(getArea(shape)) // 314.1592653589793
var shape2 = {
kind: "square",
side: 10,
}
console.log(getArea(shape2)) //100
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 函数专题
- 通常使用类似于箭头函数的形式来描述一个函数的类型
fn: (a: string) => string
- 以上的写法无法描述函数的属性,例如
description
// 定义函数的属性
type DescribableFunction = {
description: string
(someArg: string): boolean // 此处不能使用箭头,而是使用 :
}
function doSomething(fn: DescribableFunction) {
console.log(fn.description + " " + fn("foo"))
}
function isFoo(someArg: string) {
return someArg === "foo"
}
isFoo.description = "isFoo"
doSomething(isFoo)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- 构造函数同样也可以使用 TypeScript 进行类型声明
type someConstructor = {
new (s: string) => someObject
}
function fn(ctor: someConstructor) {
return new ctor('hello')
}
1
2
3
4
5
6
2
3
4
5
6
- 有些函数既可以使用 new 关键字调用,也可以直接调用,于是可以组合
interface CallOrConstruct {
(n?: number): string
new (s: string): Date
}
1
2
3
4
2
3
4
- 泛型
function firstElement<type>(arr: type[]): type | undefined {
return arr[0]
}
console.log(firstElement(["1", "2", "3", 1]))
console.log(firstElement([1, 2, 3]))
console.log(firstElement([]))
1
2
3
4
5
6
7
2
3
4
5
6
7
- 要在函数名后面加
<TypeName>
来声明这是一个泛型函数,传入的类型和返回值可以根据这个泛型函数运算- 泛型的传入可以不止一个
- 在使用泛型时,可以使用相应的声明来对传入的类型做一次类型检查
function longest<Type extends { length: number }>(a: Type, b: Type) {
if (a.length > b.length) {
return a
}
return b
}
const longerArray = longest([1, 2], [1, 2, 3])
const longerString = longest("alice", "bob")
// const notOk = longest(1, 2) // 1没有length属性,ts报错
console.log(longerArray, longerString)
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
// 使用泛型约束时返回的类型必须是传入的类型之一,而不是与目标匹配的类型
function minimumLength<Type extends { length: number }> {
obj: Type
minimum: number
}: Type {
if (obj.length >= minimum) {
return obj
}
// 这里会报错,因为 { length: minimum } 虽然符合限制,但是却不是传入的类型
return { length: minimum }
}
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
- 在调用包含泛型的函数时,可以手动声明函数的泛型
function combine<Type>(arr1: Type[], arr2: Type[]): Type[] {
return arr1.concat(arr2)
}
// const arr = combine([1,2,3], ['1']) // 将会报错,因为第一个参数决定了类型为number,而第二个参数又为string
const arr = combine<number | string>([1, 2, 3], ["1"]) // 不会报错,手动声明了泛型的范围
1
2
3
4
5
2
3
4
5
如何写一个合适的泛型
- 使用 type 本身,而不是约束,如使用
type
而不是type extends any
- 使用尽可能少的泛型参数
- 泛型参数总是要出现两次,一次是传入,一次是传出,如果只有一次需要这个类型,也许我们不需要泛型
- 使用 type 本身,而不是约束,如使用
在回调函数中加入可选参数需要进一步限制类型,尽可能不要在回调函数中传入可选参数
重载签名
function makeDate(timestamp: number): Date // 重载签名1
function makeDate(m: number, d: number, y: number): Date // 重载签名2 需要与重载签名1兼容
function makeDate(mOrTimestamp: number, d?: number, y?: number): Date {
// 实现签名,需要与前两个重载签名兼容
if (d !== undefined && y !== undefined) {
return new Date(y, mOrTimestamp, d)
} else {
return new Date(mOrTimestamp)
}
}
// 此时 makeDate 函数可以传入一个参数或者传入3个参数
makeDate(12345678) // 传入一个参数
makeDate(1, 2, 3) // 传入3个参数
// makeDate(1, 2) // 传入2个参数将会报错,因为没有定义传入两个参数的重载函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
编写重载的规则
- 尽可能使用联合类型的参数,而不是重载函数
在函数中声明 this 的类型
interface User {
id: number
admin: false
becomeAdmin: () => void
}
interface DB {
... // 其他DB的属性
filterUsers(filter: (this: User) => boolean): User[];
}
const user = {
id: 1,
admin: false,
becomeAdmin: function () {
this.admin = true
}
}
const db: DB = getDB()
db.filterUsers(function (this: User) { // 如果涉及到 this,务必使用 function 而不是箭头函数
return this.admin
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
其他需要了解到类型
- void 表示不返回值的函数的返回值
- object (并非是 Object)表示任何不是基元的值
- unknown 可以接受任何类型的值,但是 unknown 不能接受任何操作
- never 表示会报错或者终止程序的返回值,或是确定联合中已不剩任何内容
- Function 描述了全局函数类型
剩余参数
// 使用展开运算符传入剩余参数
function multiply(n: number, ...m: number[]) {
return m.map((x) => n * x)
}
// 同时也可以使用展开运算符传入参数
// TypeScript 默认数组不是不可修改的,因此会有一些需要注意的地方
const args = [8, 5]
const angle = Math.atan2(...args) // 扩张参数必须具有元组类型或传递给 rest 参数。
const args2 = [8, 5] as const // 使用 const 声明确保此数组不会发生变化
const angle2 = Math.atan2(...args2) // 此时不会再报错
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
- 参数结构
// 写法1
function sum({ a, b, c }: { a: number; b: number; c: number }) {
return a + b + c
}
// 写法2
type ABC = {
a: number
b: number
c: number
}
function sum({ a, b, c }: ABC) {
return a + b + c
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- void 返回值
// 通常定义返回值类型为void的函数可以返回值
type voidFunc = () => void
const f1: voidFunc = () => {
return true
}
const f2: voidFunc = () => true
const f3: voidFunc = function () {
return true
}
// literal function 定义了void返回值但是返回将会报错
function f(): void {
return 1 // 不能将类型“number”分配给类型“void”。
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
最近更新~: 2024/08/19, 01:05:55