TypeScript 类型
TypeScript 类型
any 类型
什么是 any 类型
any 类型表示没有任何限制,该类型的变量可以赋予任意类型的值。
使用 any 类型,相当于不进行类型检查。
let x: any
x = 1 // 正确
x = "foo" // 正确
x = true // 正确类型推断问题
对于开发者没有指定类型、TypeScript 必须自己推断类型的那些变量,如果无法推断出类型,TypeScript 就会认为该变量的类型是 any。
TypeScript 提供了一个编译选项 noImplicitAny,打开该选项,只要推断出 any 类型就会报错。
tsc --noImplicitAny xxx.ts也可以在 tsconfig.json 中配置。
// tsconfig.json
{
"compilerOptions": {
"noImplicitAny": true
}
}注意:使用 let 和 var 命令声明变量,但不赋值也不指定类型,是不会报错的。
污染问题
TypeScript 的 any 类型会污染其他类型,因为 any 类型的变量可以赋值给其他类型的变量。
let x: any = "hello"
let y: number
y = x // 不报错
y * 123 // 不报错
y.toFixed() // 不报错unknown 类型
什么是 unknown 类型
所有类型都可以赋值给 unknown 类型。
let x: unknown
x = true // 正确
x = 42 // 正确
x = "Hello World" // 正确unknown 类型赋值给 any 和 unknown 以外类型的变量都会报错。
let v: unknown = 123
let v1: boolean = v // 报错
let v2: number = v // 报错不能直接调用 unknown 类型变量的方法和属性。
let v1: unknown = { foo: 123 }
v1.foo // 报错
let v2: unknown = "hello"
v2.trim() // 报错
let v3: unknown = (n = 0) => n + 1
v3() // 报错unknown 类型变量能够进行的运算是有限的,只能进行比较运算 (运算符 ==、===、!=、!==、||、&&、?)、取反运算 (运算符 !)、typeof 运算符和 instanceof 运算符。
let a: unknown = 1
a + 1 // 报错
a === 1 // 正确如何使用 unknown 类型
unknown 类型可以通过类型缩小来使用。
let a: unknown = 1
if (typeof a === "number") {
let r = a + 10 // 正确
}never 类型
什么是 never 类型
never 类型表示的是那些永不存在的值的类型。
不能给 never 赋值,因为 never 表示的是那些永不存在的值的类型。
let x: never
x = 123 // 报错never 类型可以赋值给任意其他类型。
function f(): never {
throw new Error("Error")
}
let v1: number = f() // 不报错
let v2: string = f() // 不报错
let v3: boolean = f() // 不报错never 类型的作用
never 类型是那些总是会抛出异常或根本就不会有返回值的函数表达式或箭头函数表达式的返回值类型。
function error(message: string): never {
throw new Error(message)
}never 类型还用在一些类型运算之中,保证类型运算的完整性。
function fn(x: string | number) {
if (typeof x === "string") {
// ...
} else if (typeof x === "number") {
// ...
} else {
x // x 在这里是 never 类型
}
}boolean 类型
boolean 类型只包含 true 和 false 两个布尔值。
let isDone: boolean = falsenumber 类型
number 类型包含所有整数和浮点数。
const x: number = 123
const y: number = 3.14
const z: number = 0xffffstring 类型
string 类型包含所有字符串。
const x: string = "hello"
const y: string = `${x} world`bigint 类型
bigint 类型包含所有的大整数。
const x: bigint = 123n
const y: bigint = 0xffffnsymbol 类型
symbol 类型包含所有的 Symbol 值。
const x: symbol = Symbol()unique symbol 类型是 symbol 类型的子类型,它表示单个的、某个具体的 Symbol 值。
const x: unique symbol = Symbol() // 只能用 const 声明undefined 类型
undefined 类型只包含一个值 undefined,表示未定义 (即还未给出定义,以后可能会有定义)。
let x: undefined = undefinedundefined 作为值时,它有一个特殊的地方,任何其他类型的变量都可以赋值为 undefined。
但是这样会有如下问题。
const obj: object = undefined
obj.toString() // 编译不报错,运行就报错为此,TypeScript 提供了一个编译选项 strictNullChecks,打开该选项,undefined 就不能赋值给其他类型的变量 (除了 any 类型和 unknown 类型)。
tsc --strictNullChecks xxx.ts也可以在 tsconfig.json 中配置。
// tsconfig.json
{
"compilerOptions": {
"strictNullChecks": true
}
}null 类型
null 类型只包含一个值 null,表示为空 (即此处没有值)。
const x: null = nullnull 作为值时,它有一个特殊的地方,任何其他类型的变量都可以赋值为 null。
但是这样会有如下问题。
const obj: object = null
obj.toString() // 编译不报错,运行就报错为此,TypeScript 提供了一个编译选项 strictNullChecks,打开该选项,null 就不能赋值给其他类型的变量 (除了 any 类型和 unknown 类型)。
tsc --strictNullChecks xxx.ts也可以在 tsconfig.json 中配置。
// tsconfig.json
{
"compilerOptions": {
"strictNullChecks": true
}
}包装对象类型
在 JavaScript 中,基本类型是没有属性和方法的,但是当我们对一个基本类型使用属性和方法时,JavaScript 会自动将这个基本类型包装成一个对象,然后调用对象的属性和方法。
例如,我们可以对字符串使用 length 属性来获取字符串的长度。
const str = "hello"
console.log(str.length) // 5在 JavaScript 中包装对象有 String、Number、Boolean。
String()、Number()、Boolean() 这三个构造函数执行后可以直接获取某个原始类型值的包装对象。
在 TypeScript 中,我们可以使用 String、Number、Boolean 来表示这些包装对象类型。
const s1: String = "hello" // 正确
const s2: String = new String("hello") // 正确
const s3: string = "hello" // 正确
const s4: string = new String("hello") // 报错建议只使用小写类型,不使用大写类型。因为绝大部分使用原始类型的场合,都是使用字面量,不使用包装对象。而且,TypeScript 把很多内置方法的参数,定义成小写类型,使用大写类型会报错。
const n1: number = 1
const n2: Number = 1
Math.abs(n1) // 1
Math.abs(n2) // 报错Object 类型
Object 类型代表 JavaScript 语言里面的广义对象。所有可以转成对象的值,都是 Object 类型,这囊括了几乎所有的值。
let obj: Object
obj = true
obj = "hi"
obj = 1
obj = { foo: 123 }
obj = [1, 2]
obj = (a: number) => a + 1空对象 {} 是 Object 类型的简写形式,所以使用 Object 时常常用空对象代替。
let obj: {}
obj = true
obj = "hi"
obj = 1
obj = { foo: 123 }
obj = [1, 2]
obj = (a: number) => a + 1object 类型
object 类型代表 JavaScript 里面的狭义对象,即可以用字面量表示的对象,只包含对象、数组和函数,不包括原始类型的值。
let obj: object
obj = { foo: 123 }
obj = [1, 2]
obj = (a: number) => a + 1
obj = true // 报错
obj = "hi" // 报错
obj = 1 // 报错注意,无论是 Object 类型,还是 object 类型,都只包含 JavaScript 内置对象原生的属性和方法,用户自定义的属性和方法都不存在于这两个类型之中。
const o1: Object = { foo: 0 }
const o2: object = { foo: 0 }
o1.toString() // 正确
o1.foo // 报错
o2.toString() // 正确
o2.foo // 报错值类型
单个值也是一种类型,称为 “值类型”。
let x: "hello"
x = "hello" // 正确
x = "world" // 报错联合类型
联合类型指的是多个类型组成的一个新类型,使用符号 | 表示。
联合类型 A|B 表示,任何一个类型只要属于 A 或 B,就属于联合类型 A|B。
let x: string | number
x = 123 // 正确
x = "abc" // 正确交叉类型
交叉类型指的多个类型组成的一个新类型,使用符号 & 表示。
交叉类型 A&B 表示,任何一个类型必须同时属于 A 和 B,才属于交叉类型 A&B,即交叉类型同时满足 A 和 B 的特征。
let obj: { foo: string } & { bar: string }
obj = {
foo: "hello",
bar: "world",
}数组类型
数组类型的定义
数组类型有两种定义方式。
第一种是使用 [] 表示数组,数组中的每个元素都必须是相同的类型。
let arr: number[] = [1, 2, 3]
let arr1: (number | string)[] = [1, "2", 3]第二种是使用 Array 表示数组,数组中的每个元素都必须是相同的类型。
let arr: Array<number> = [1, 2, 3]
let arr1: Array<number | string> = [1, "2", 3]数组类型声明了以后,成员数量是不限制的,任意数量的成员都可以,也可以是空数组。
数组的类型推断
如果声明数组时没有指定类型,TypeScript 会自动进行类型推断。
如果变量的初始值是空数组,那么 TypeScript 会推断数组类型是 any[]。
// 推断为 any[]
const arr = []后面,为这个数组赋值时,TypeScript 会自动更新类型推断。
const arr = []
arr // 推断为 any[]
arr.push(123)
arr // 推断类型为 number[]
arr.push("abc")
arr // 推断类型为 (string|number)[]如果变量的初始值不是空数组,那么 TypeScript 会根据初始值推断数组类型,类型推断也不会自动更新。
// 推断类型为 number[]
const arr = [123]
arr.push("abc") // 报错只读数组
第一种是使用 readonly 关键字,表示数组是只读的,不能修改。
const arr: readonly number[] = [0, 1]
arr[1] = 2 // 报错
arr.push(3) // 报错第二种是使用 ReadonlyArray 类型,表示数组是只读的,不能修改。
const arr: ReadonlyArray<number> = [0, 1]第三种是使用 const 断言。
const arr = [0, 1] as const由于只读数组是数组的父类型,所以它不能代替数组。
function getSum(s: number[]) {
}
const arr: readonly number[] = [1, 2, 3]
getSum(arr) // 报错
// 可以使用类型断言,将只读数组转换为普通数组
getSum(arr as number[])多维数组
使用 T[][] 的形式,表示二维数组,T 是最底层数组成员的类型。
const arr: number[][] = [[1, 2], [3, 4]]元组类型
元组类型的定义
元组表示成员类型可以自由设置的数组,即数组的各个成员的类型可以不同。
元组必须明确声明每个成员的类型。
const s: [string, string, boolean] = ["a", "b", true]使用元组时,必须明确给出类型声明,否则 TypeScript 会把一个值自动推断为数组。
let a = [1, true] // 会推断为 (number | boolean)[]元组成员的类型可以添加问号后缀 (?),表示该成员是可选的,问号只能用于元组的尾部成员。
let a: [number, number?] = [1]使用扩展运算符 (...),可以表示不限成员数量的元组,扩展运算符用在元组的任意位置都可以,但是它后面只能是数组或元组。
let a: [number, ...number[]] = [1, 2, 3]
let b: [...number[], number] = [1, 2, 3]只读元组
第一种是使用 readonly 关键字,表示元组是只读的,不能修改。
const arr: readonly [number, string] = [0, "1"]第二种是使用 Readonly,表示元组是只读的,不能修改。
const arr: Readonly<[number, string]> = [0, "1"]第三种是使用 const 断言。
const arr = [0, "1"] as const由于只读元组是元组的父类型,所以它不能代替元组。
function distanceFromOrigin([x, y]: [number, number]) {
return Math.sqrt(x ** 2 + y ** 2)
}
let point = [3, 4] as const
distanceFromOrigin(point) // 报错
// 可以使用类型断言,将只读元组转换为普通元组
distanceFromOrigin(point as [number, number])成员数量的推断
如果没有可选成员和扩展运算符,TypeScript 会推断出元组的成员数量 (即元组长度)。
如果包含了可选成员,TypeScript 会推断出可能的成员数量。
如果使用了扩展运算符,TypeScript 就无法推断出成员数量。
