类型
基础类型
boolean 类型
let type: boolean = true
number 类型
let type: number = 123
string 类型
let type: string = 'typescript'
null 和 undefined 类型
let u: undefined = undefined
let n: null = null
默认情况下 null 和 undefined 是所有类型的子类型。 就是说你可以把 null 和 undefined 赋值给其他类型。
如果你在 tsconfig.json 指定了"strictNullChecks":true ,null 和 undefined 只能赋值给 void 和它们各自的类型。
object 类型
let obj: object = { x: 1 }
bigInt 类型
let big: bigint = 100n
虽然 number 和 bigint 都表示数字,但是这两个类型不兼容。
let big: bigint = 100n
let num: number = 6
big = num
num = big
symbol 类型
let sym: symbol = Symbol('me')
其他类型
Array 类型
let list1: number[] = [1, 2, 3]
let list2: Array<number> = [1, 2, 3] // Array<number>泛型语法
let arr: (number | string)[] = [1, 'b', 2, 'c']
interface Arrobj {
name: string
age: number
}
let arr3: Arrobj[] = [{ name: 'jimmy', age: 22 }]
Enum 类型
数字枚举
enum Direction {
NORTH,
SOUTH,
EAST,
WEST,
}
let dir: Direction = Direction.NORTH
默认情况下,NORTH 的初始值为 0,其余的成员会从 1 开始自动增长。换句话说,Direction.SOUTH 的值为 1,Direction.EAST 的值为 2,Direction.WEST 的值为 3。当然我们也可以设置 NORTH 的初始值,比如:
enum Direction {
NORTH = 3,
SOUTH,
EAST,
WEST,
}
let dir: Direction = Direction.NORTH
字符串枚举
enum Direction {
NORTH = 'NORTH',
SOUTH = 'SOUTH',
EAST = 'EAST',
WEST = 'WEST',
}
let dir: Direction = Direction.NORTH
异构枚举
enum Enum {
A,
B,
C = 'C',
D = 'D',
E = 8,
F,
}
Tuple 元组 类型
元组可用于定义具有有限数量的未命名属性的类型。每个属性都有一个关联的类型。使用元组时,必须提供每个属性的值。为了更直观地理解元组的概念,我们来看一个具体的例子:
let x: [string, number]
// 类型必须匹配且个数必须为2
x = ['hello', 10] // OK
x = ['hello', 10, 10] // Error
x = [10, 'hello'] // Error
解构赋值
let employee: [number, string] = [1, 'Semlinker']
let [id, username] = employee
console.log(`id: ${id}`)
console.log(`username: ${username}`)
// id: 1
// username: Semlinker
这里需要注意的是,在解构赋值时,如果解构数组元素的个数是不能超过元组中元素的个数,否则也会出现错误,比如:
let employee: [number, string] = [1, 'Semlinker']
let [id, username, age] = employee
// Tuple type '[number, string]' of length '2' has no element at index '2'.
可选类型
let optionalTuple: [string, boolean?]
optionalTuple = ['Semlinker', true]
console.log(`optionalTuple : ${optionalTuple}`)
optionalTuple = ['Kakuqo']
console.log(`optionalTuple : ${optionalTuple}`)
剩余类型
type RestTupleType = [number, ...string[]]
let restTuple: RestTupleType = [666, 'Semlinker', 'Kakuqo', 'Lolo']
console.log(restTuple[0])
console.log(restTuple[1])
只读
const point: readonly [number, number] = [10, 20]
// Cannot assign to '0' because it is a read-only property.
point[0] = 1
// Property 'push' does not exist on type 'readonly [number, number]'.
point.push(0)
// Property 'pop' does not exist on type 'readonly [number, number]'.
point.pop()
// Property 'splice' does not exist on type 'readonly [number, number]'.
point.splice(1, 1)
Void 类型
void 表示没有任何类型,和其他类型是平等关系,不能直接赋值:
let a: void
let b: number = a // Error
你只能为它赋予 null 和 undefined(在 strictNullChecks 未指定为 true 时)。声明一个 void 类型的变量没有什么大用,我们一般也只有在函数没有返回值时去声明。
// 声明函数返回值为void
function warnUser(): void {
console.log('This is my warning message')
}
Never 类型
never 类型表示的是那些永不存在的值的类型。 例如,never 类型是那些总是会抛出异常或根本就不会有返回值的函数表达式或箭头函数表达式的返回值类型。
// 返回never的函数必须存在无法达到的终点
function error(message: string): never {
throw new Error(message)
}
function infiniteLoop(): never {
while (true) {}
}
在 TypeScript 中,可以利用 never 类型的特性来实现全面性检查,具体示例如下:
type Foo = string | number
function controlFlowAnalysisWithNever(foo: Foo) {
if (typeof foo === 'string') {
// 这里 foo 被收窄为 string 类型
} else if (typeof foo === 'number') {
// 这里 foo 被收窄为 number 类型
} else {
// foo 在这里是 never
const check: never = foo
}
}
注意在 else 分支里面,我们把收窄为 never 的 foo 赋值给一个显示声明的 never 变量。如果一切逻辑正确,那么这里应该能够编译通过。但是假如后来有一天你的同事修改了 Foo 的类型:
type Foo = string | number | boolean
然而他忘记同时修改 controlFlowAnalysisWithNever 方法中的控制流程,这时候 else 分支的 foo 类型会被收窄为 boolean 类型,导致无法赋值给 never 类型,这时就会产生一个编译错误。通过这个方式,我们可以确保 controlFlowAnalysisWithNever 方法总是穷尽了 Foo 的所有可能类型。 通过这个示例,我们可以得出一个结论:使用 never 避免出现新增了联合类型没有对应的实现,目的就是写出类型绝对安全的代码。
never 类型同 null 和 undefined 一样,也是任何类型的子类型,也可以赋值给任何类型。 但是没有类型是 never 的子类型或可以赋值给 never 类型(除了 never 本身之外),即使 any 也不可以赋值给 never
Any 类型
在 TypeScript 中,任何类型都可以被归为 any 类型。这让 any 类型成为了类型系统的顶级类型(也被称作全局超级类型)。
let notSure: any = 666
notSure = 'Semlinker'
notSure = false
any 类型本质上是类型系统的一个逃逸舱。作为开发者,这给了我们很大的自由:TypeScript 允许我们对 any 类型的值执行任何操作,而无需事先执行任何形式的检查。比如:
let value: any
value.foo.bar // OK
value.trim() // OK
value() // OK
new value() // OK
value[0][1] // OK
变量如果在声明的时候,未指定其类型,那么它会被识别为任意值类型
let something
something = 'seven'
something = 7
something.setName('Tom')
等价于
let something: any
something = 'seven'
something = 7
something.setName('Tom')
Unknown 类型
所有类型都可以赋值给 any,所有类型也都可以赋值给 unknown。这使得 unknown 成为 TypeScript 类型系统的另一种顶级类型(另一种是 any)。
let value: unknown
value = true // OK
value = 42 // OK
value = 'Hello World' // OK
value = [] // OK
value = {} // OK
value = Math.random // OK
value = null // OK
value = undefined // OK
value = new TypeError() // OK
value = Symbol('type') // OK
unknown 与 any 的最大区别是: 任何类型的值可以赋值给 any,同时 any 类型的值也可以赋值给任何类型。unknown 任何类型的值都可以赋值给它,但它只能赋值给 unknown 和 any
let value: unknown
let value1: unknown = value // OK
let value2: any = value // OK
let value3: boolean = value // Error
let value4: number = value // Error
let value5: string = value // Error
let value6: object = value // Error
let value7: any[] = value // Error
let value8: Function = value // Error
unknown 类型只能被赋值给 any 类型和 unknown 类型本身。直观地说,这是有道理的:只有能够保存任意类型值的容器才能保存 unknown 类型的值。毕竟我们不知道变量 value 中存储了什么类型的值。
现在让我们看看当我们尝试对类型为 unknown 的值执行操作时会发生什么。以下是我们在之前 any 章节看过的相同操作:
let value: unknown
value.foo.bar // Error
value.trim() // Error
value() // Error
new value() // Error
value[0][1] // Error
将 value 变量类型设置为 unknown 后,这些操作都不再被认为是类型正确的。通过将 any 类型改变为 unknown 类型,我们已将允许所有更改的默认设置,更改为禁止任何更改。
函数类型
函数声明
function sum(x: number, y: number): number {
return x + y
}
函数表达式
let mySum: (x: number, y: number) => number = function (
x: number,
y: number
): number {
return x + y
}
用接口定义函数类型
interface SearchFunc {
(source: string, subString: string): boolean
}
采用函数表达式接口定义函数的方式时,对等号左侧进行类型限制,可以保证以后对函数名赋值时保证参数个数、参数类型、返回值类型不变。
可选参数
function buildName(firstName: string, lastName?: string) {
if (lastName) {
return firstName + ' ' + lastName
} else {
return firstName
}
}
let tomcat = buildName('Tom', 'Cat')
let tom = buildName('Tom')
参数默认值
function buildName(firstName: string, lastName: string = 'Cat') {
return firstName + ' ' + lastName
}
let tomcat = buildName('Tom', 'Cat')
let tom = buildName('Tom')
剩余参数
function push(array: any[], ...items: any[]) {
items.forEach(function (item) {
array.push(item)
})
}
let a = []
push(a, 1, 2, 3)
函数重载
type Types = number | string
function add(a: number, b: number): number
function add(a: string, b: string): string
function add(a: string, b: number): string
function add(a: number, b: string): string
function add(a: Types, b: Types) {
if (typeof a === 'string' || typeof b === 'string') {
return a.toString() + b.toString()
}
return a + b
}
const result = add('Semlinker', ' Kakuqo')
result.split(' ')
字面量类型
在 TypeScript 中,字面量不仅可以表示值,还可以表示类型,即所谓的字面量类型。
目前,TypeScript 支持 3 种字面量类型:字符串字面量类型、数字字面量类型、布尔字面量类型,对应的字符串字面量、数字字面量、布尔字面量分别拥有与其值一样的字面量类型,具体示例如下:
{
let specifiedStr: 'this is string' = 'this is string'
let specifiedNum: 1 = 1
let specifiedBoolean: true = true
}
比如 'this is string' (这里表示一个字符串字面量类型)类型是 string 类型(确切地说是 string 类型的子类型),而 string 类型不一定是 'this is string'(这里表示一个字符串字面量类型)类型,如下具体示例:
{
let specifiedStr: 'this is string' = 'this is string'
let str: string = 'any string'
specifiedStr = str // ts(2322) 类型 '"string"' 不能赋值给类型 'this is string'
str = specifiedStr // ok
}
字符串字面量类型
let hello: 'hello' = 'hello';
hello = 'hi'; // ts(2322) Type '"hi"' is not assignable to type '"hello"'
实际上,定义单个的字面量类型并没有太大的用处,它真正的应用场景是可以把多个字面量类型组合成一个联合类型(后面会讲解),用来描述拥有明确成员的实用的集合。
如下代码所示,我们使用字面量联合类型描述了一个明确、可 'up' 可 'down' 的集合,这样就能清楚地知道需要的数据结构了。
type Direction = 'up' | 'down';
function move(dir: Direction) {
// ...
}
move('up'); // ok
move('right'); // ts(2345) Argument of type '"right"' is not assignable to parameter of type 'Direction'
通过使用字面量类型组合的联合类型,我们可以限制函数的参数为指定的字面量类型集合,然后编译器会检查参数是否是指定的字面量类型集合里的成员。
因此,相较于使用 string 类型,使用字面量类型(组合的联合类型)可以将函数的参数限定为更具体的类型。这不仅提升了程序的可读性,还保证了函数的参数类型,可谓一举两得。
数字字面量类型及布尔字面量类型
数字字面量类型和布尔字面量类型的使用与字符串字面量类型的使用类似,我们可以使用字面量组合的联合类型将函数的参数限定为更具体的类型,比如声明如下所示的一个类型 Config:
interface Config {
size: 'small' | 'big';
isEnable: true | false;
margin: 0 | 2 | 4;
}
在上述代码中,我们限定了 size 属性为字符串字面量类型 'small' | 'big',isEnable 属性为布尔字面量类型 true | false(布尔字面量只包含 true 和 false,true | false 的组合跟直接使用 boolean 没有区别),margin 属性为数字字面量类型 0 | 2 | 4。
Number、String、Boolean、Symbol
首先,我们来回顾一下初学 TypeScript 时,很容易和原始类型 number、string、boolean、symbol 混淆的首字母大写的 Number、String、Boolean、Symbol 类型,后者是相应原始类型的包装对象,姑且把它们称之为对象类型。 从类型兼容性上看,原始类型兼容对应的对象类型,反过来对象类型不兼容对应的原始类型。 下面我们看一个具体的示例:
let num: number
let Num: Number
Num = num // ok
num = Num // ts(2322)报错
在示例中的第 3 行,我们可以把 number 赋给类型 Number,但在第 4 行把 Number 赋给 number 就会提示 ts(2322) 错误。
object、Object 和 {}
object(首字母小写,以下称“小 object”)、Object(首字母大写,以下称“大 Object”)和 {}(以下称“空对象”)。
小 object 代表的是所有非原始类型,也就是说我们不能把 number、string、boolean、symbol 等 原始类型赋值给 object。在严格模式下,null 和 undefined 类型也不能赋给 object。
let lowerCaseObject: object
lowerCaseObject = 1 // ts(2322)
lowerCaseObject = 'a' // ts(2322)
lowerCaseObject = true // ts(2322)
lowerCaseObject = null // ts(2322)
lowerCaseObject = undefined // ts(2322)
lowerCaseObject = {} // ok
大 Object 代表所有拥有 toString、hasOwnProperty 方法的类型,所以所有原始类型、非原始类型都可以赋给 Object。同样,在严格模式下,null 和 undefined 类型也不能赋给 Object。
let upperCaseObject: Object
upperCaseObject = 1 // ok
upperCaseObject = 'a' // ok
upperCaseObject = true // ok
upperCaseObject = null // ts(2322)
upperCaseObject = undefined // ts(2322)
upperCaseObject = {} // ok
从上面示例可以看到,大 Object 包含原始类型,小 object 仅包含非原始类型,所以大 Object 似乎是小 object 的父类型。实际上,大 Object 不仅是小 object 的父类型,同时也是小 object 的子类型。
{}空对象类型和大 Object 一样,也是表示原始类型和非原始类型的集合,并且在严格模式下,null 和 undefined 也不能赋给 {} ,如下示例:
let ObjectLiteral: {}
ObjectLiteral = 1 // ok
ObjectLiteral = 'a' // ok
ObjectLiteral = true // ok
ObjectLiteral = null // ts(2322)
ObjectLiteral = undefined // ts(2322)
ObjectLiteral = {} // ok
type isLiteralCaseObjectExtendsUpperCaseObject = {} extends Object
? true
: false // true
type isUpperCaseObjectExtendsLiteralCaseObject = Object extends {}
? true
: false // true
upperCaseObject = ObjectLiteral
ObjectLiteral = upperCaseObject
相关信息
{}、大 Object 是比小 object 更宽泛的类型(least specific),{} 和大 Object 可以互相代替,用来表示原始类型(null、undefined 除外)和非原始类型;而小 object 则表示非原始类型。