1.类型注释
TypeScript类型种类如下
TypeScript基本类型:
number,string,boolean,bigint,symbol,null,undefined,never,void,any,unknown,值类型
对象类型:
class,object,Array,interface,JavaScript构造函数类型
高级类型:
enum,tuple,type,联合类型,交叉类型
1.1 TypeScript基本类型
1.1.1 TypeScrip和JavaScript基本类型区别
-
大小写区别:
TypeScript类型和JavaScript类型大小写不同。
-
性能区别:
JavaScript中这些类型是作为构造函数或工厂函数使用,TypeScript这些类型就是纯粹的类型,没有这些功能。虽然使用两者进行注释是等效的,但是使用TypeScript中这些类型会有更好的性能和内存管理能力(参考ChatGPT)。
1.1.2 void和never
never和void是语义相近的类型,never比void更加严格。
-
never表示不能是任何类型:
// 使用后文提到的“类型别名”和“交叉类型”来获得never类型 type variableA = string type variableB = number //得到的variableC是never类型,因为string和number类型不可能取交集 type variableC = variableA & variableB // 指定一个函数返回值为never类型,这个函数永远不能被赋值,因为函数默认返回undefined let funcEmpty: () => never
-
void表示值只能是undefined:
// 指定一个函数为空函数 let funcEmpty: () => void = () => {}
1.1.3 any和unknown
-
any表示任何类型:
any表示任何类型,不推荐使用,使用后效果和JavaScript相同。
-
unknown表示未知类型,操作需要检查:
unknown表示任何类型,含义和any相同,但是比any严格,可以使用。在调用变量属性方法,或进行其它操作时不经过检查都是不合法的。
let variable: unknown = 'hello' console.log(variable.length) // 报错,不能对unknown类型变量进行操作 if(typeof variable === 'string') console.log(variable.length) // 合法,提前检测了类型
1.1.4 值类型
当声明时指定了具体值之后,变量赋值只能赋为该值,否则会发生错误
let variableA: 2
variableA = 2
1.1.5 其它基本类型
直接进行类型注释即可,较为简单
let variable: string = 'hello'
1.2 对象类型
1.2.1 object和Object
-
不要使用object和Object进行注释:
在1.1.1中提到了用object和Object进行类型注释是等效的。但是实际上不推荐使用object和Object进行类型注释。使用object和Object进行对象类型注释,TypeScript会认为对象不可修改,必须保持和空对象一致。
let obj: object = { name: "Danny", age: 21 } console.log(obj.name) // 报错,不允许访问name属性 obj.gender = "male" // 报错,不允许修改对象 console.log(obj.toString) // 合法,空对象原型上有toString方法
-
使用对象字面量进行注释:
如果要类型注释对象类型,推荐使用对象字面量。不过要注意的是,TypeScript是“静态类型检查语言”(参考编译原理),对于类型检查是严格的,在变量初始化时指定了要和对象字面量形式一致,因此下面添加或删除对象属性都是非法的。
let obj: { name: string, age: number } = { name: "Danny", age: 21 } console.log(obj.name) // 合法 obj.gender = "male" // 不合法 console.log(obj.toString) // 合法
-
其它推荐的对象注释方式:
在实际应用时使用对象字面量注释不具有复用性,通常使用“接口”,“类型别名”,“类”进行注释
1.2.2 Array
TypeScript中数组类型注释较为简单。string[]
类型注释是泛型类型注释Array<string>
的简写,效果相同。
在使用Array时要另外注意一点Array类型在类型推断时会被推断为数组元素个数不确定,详情参考“2.2.2数组推断”或“TypeScript泛型”博文。
// 类型注释一维数组
let ar1Dim: string[] = ['1', '2', '3']
// 类型注释二维数组
let ar2Dim: string[][] = [['11', '12', '13'], ['21', '22', '23']]
// 还可以使用内置Array泛型来声明,但是声明高维数组较麻烦
const ar1Dim: Array<string> = ['1', '2', '3']
const ar2Dim: Array<Array<string>> = [['11', '12', '13'], ['21', '22', '23']]
1.2.3 Function
1.2.3.1 声明函数
-
“函数表达式”声明函数:
function func(arg1: string, arg2: number): void {}
-
“赋值语句”声明函数:
const func = function(arg1: string, arg2: number): void {}
-
“箭头函数”声明函数:
const func = (arg1: string, arg2: number): void => {}
-
“接口或类型别名”声明函数:
// 接口 interface FuncInterface { (arg1: string, arg2: number): void // 语法糖:对象字面量中定义函数返回值可以用“:返回值”替代 } const funcI: FuncInterface = (arg1: string, arg2: number) => {} // 类型别名 type FuncAlias = (arg1: string, arg2: number) => void // 语法糖:描述箭头函数类型 const funcA = (arg1: string, arg2: number) => {}
-
“函数签名”声明函数:
// 函数签名可以由“接口”,“类型别名”实现 interface Func { description: string; (arg1: string, arg2: number): void } function func(arg1: string, arg2: number): void {} func.description = "this is a function" const funcS: Func = func
1.2.3.2 注释函数
-
不要使用Function:
不要使用Function进行类型注释:在最后两种声明方法中和“object和Object”一样,在这里也不推荐用Function进行注释。Function太过于宽泛,没有指明参数类型和返回值类型。
-
使用throw可以不用考虑返回值:
如果函数中使用throw,说明函数无法正常返回值,当然默认返回undefined也是不生效的。在这里已经抛出了错误,所以不需要考虑和函数返回值的类型注释保持一致。
function test(): string { throw new TypeError('test') // 合法 } test() // 合法
1.2.4 Interface
1.2.4.1 描述接口
在描述接口换行时可以使用逗号或分号
interface STU {
id: string,
name: string,
gender: string,
age: number,
getDetailInfo(id: string): { grade: number, location: string }
}
1.2.4.2 扩展接口
-
使用extends关键字扩展接口:
interface A { name: string } interface B extends A { // 此时实现接口B也必须含有接口A中的name age: number }
-
使用覆写方式扩展接口:
interface A { name: string } interface A { age: number // 此时效果和extends效果相同 }
-
使用交叉类型扩展接口:
interface A { name: string } interface B { age: number } type C = A & B
1.2.4.3 实现接口
1.2.4.3.1 类实现接口
-
使用implements实现接口无法进行类型推断:
后文要提到的“类型推断”是指在不进行类型注释时TypeScript会推测变量类型是哪一种,这类似于JavaScript的V8引擎中的“JIT”优化中的热处理(详情参考博客“JavaScript编译器和解释器”)。由于这里只有implements关键字,TypeScript无法推测类实现的接口中的方法的参数类型。
class SDUer implements STU { // STU接口在上文已经实现 constructor(public id: string, public name: string, public gender: string, public age: number) {} getDetailInfo(id) { return { grade: 100, location: '6' } } } let sduer = new SDUer('123', 'Danny', 'male', 21) sduer.getDetailInfo(1234) // 虽然接口中指明了该方法接收的参数应为字符串,但是编译器不会报错
-
解决类实现接口可能出现的潜在错误:
由于implements导致无法进行类型推断,因此函数参数默认为any,这样会影响静态类型检查,可能会出现错误。
可行的解决方案:
-
配置tsconfig.json,开启严格模式,这样不允许any类型出现
-
实现接口方法时主动添加参数类型
getDetailInfo(id: string) { return { grade: 100, location: '6' } }
-
声明对象时指定对象就是接口的实现
let sduer: STU = new SDUer('123', 'Danny', 'male', 21)
-
-
类不能实现接口描述的构造函数:
接口或类型别名可能使用这样的语法糖来描述构造函数:
interface Constructor { new (arg1: string, arg2: number): { name: string, age: number } } type Constructor = { new (arg1: string, arg2: number): { name: string, age: number } }
但是类不能实现这样的接口,因为构造函数属于类的独有成员,不应该由接口描述,不论尝试怎样实现,编译器都会报错。
接口描述构造函数的场景应用较少,比较常见的一个场景就是“使用工厂函数创建对象
// 通过接口来描述构造函数 interface Constructor { new (name: string, age: number): { name: string, age: number } } // 构造构造函数所在的类 class Person { constructor(public name: string, public age: number) {} } // 工厂函数 function factory(constructor: Constructor, name: string, age: number): Person { return new constructor(name, age) } // 构造函数通过类名调用,所以将Person类传递过去 console.log(factory(Person, 'Danny', 21))
1.2.4.3.2 对象实现接口
const sduStu: STU = {
id: '1234',
name: 'Danny',
gender: 'male',
age: 21,
getDetailInfo(id: string) { // id可以不写类型注释,因为通过sdustu: STU可以推断出这里的id必须是string类型
return {
grade: 100,
location: '6'
}
}
}
1.2.4.3.3 函数实现接口
函数实现接口的方法起始已经委婉地在上1.2.3中提到,就是“接口声明函数”和“函数签名声明函数”两种方式。
// 用接口声明函数
interface Cal {
(a: number, b: number): number
}
const cal: Cal = (a, b) => a + b
1.2.5 其它构造函数类型
直接进行注释即可,较为简单
// JavaScript构造函数类型
let str: String = 'hello'
// 自定义class类型
class STU {
public name: string
public age: number
constructor(name: string, age: number) {
this.name = name
this.age = age
}
}
let stu: STU = new STU("Danny", 21)
1.3 高级类型
1.3.1 enum
使用enum声明一个枚举,枚举中又声明了若干枚举值,每一个枚举值又会有一个关联值。
1.3.1.1 默认枚举
默认从第一个枚举值开始关联0,往后关联1,2,3
enum Fruit {
Apple,
Orange,
Melon
}
typescript的默认枚举在实现上实际是一个对象,对象中进行了反向映射
let Fruit = {
Apple: 0,
Orange: 1,
Melon: 2,
'0': 'Apple',
'1': 'Orange',
'2': 'Melon'
}
Fruit.Apple
或Fruit[0]
这两种访问形式都有应用场景
1.3.1.2 数值枚举
数值枚举要求给枚举值赋值为数值
enum Fruit {
Apple = 2,
Orange,
Melon
}
反向映射结果为
let Fruit = {
Apple: 2,
Orange: 3,
Melon: 4,
'2': 'Apple',
'3': 'Orange',
'4': 'Melon'
}
也可以给全部枚举值指定关联值
enum Fruit {
Apple = 2,
Orange = 1,
Melon = 4
}
反向映射结果为
let Fruit = {
Apple: 2,
Orange: 1,
Melon: 4,
'2': 'Apple',
'1': 'Orange',
'4': 'Melon'
}
1.3.1.3 字符串枚举
字符串枚举要求每个枚举值必须赋一个字符串关联值
enum Fruit {
Apple = 'Apple',
Orange = 'Orange',
Melon = 'Melon'
}
反向映射结果为
let Fruit = {
Apple: 'Apple',
Orange: 'Orange',
Melon: 'Melon',
}
1.3.1.4 混合枚举
混合枚举要求每个枚举值必须赋一个字符串或数值关联值。不推荐使用混合枚举,一般枚举值都有相关性,混合枚举破坏了相关的联系。
enum Fruit {
Apple = 0,
Orange = 'Orange',
Melon = 'Melon'
}
反向映射结果为
let Fruit = {
Apple: '0',
0: 'Apple',
Orange: 'Orange',
Melon: 'Melon'
}
1.3.1.5 常量枚举
常量枚举在编译时会被删除,不产生枚举对应的对象形式,效率较高。因此声明常量枚举后直接访问枚举是非法的,只能访问枚举值。
const enum Fruit {
Apple,
Orange,
Melon
}
console.log(Fruit) // 非法的
console.log(Fruit.Apple) // 合理的
因为不会生成对象形式,因此最后结果只会输出0
1.3.1.6 使用枚举的时机
在typescript中枚举,数组,对象相互之间的功能比较相似,需要在特定场景进行分析。
-
语意明确时用枚举:
当常量具有清晰明确的语意时,应该使用枚举类型。枚举类型为常量提供了明确的命名和类型,可以使代码更加可读和维护。
enum DaysOfWeek { Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday } function isWeekend(day: DaysOfWeek): boolean { return day === DaysOfWeek.Saturday || day === DaysOfWeek.Sunday; } isWeekend(DaysOfWeek.Saturday); // true
-
值不需要变动用对象或数组:
如果固定的值不会发生变化,可以考虑使用常量数组或对象。
const weekdays = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"]; const maxFileSize = { txt: 1024, jpg: 2048, gif: 3072 };
-
值需要从动态来源获得用对象或数组:
如果值需要从动态来源获取,例如从后端API中获取,则应该使用对象或数组类型,并根据需要进行类型定义。
interface User { id: number; name: string; role: "admin" | "member"; } function hasAdmin(users: User[]) { return users.some(user => user.role === "admin"); } // 从API中获取用户信息 fetch("/users") .then(response => response.json()) .then(data => { if (hasAdmin(data)) { // do something } });
1.3.1.7 枚举类型注释方法
枚举和枚举值都可以作为类型来进行注释。
-
(常用)注释为枚举类型:
将变量注释为枚举类型,指的是变量的取值范围固定在枚举值的关联值当中。
let variableEnum: Fruit = 0 if(variableEnum === Fruit.Apple) console.log('Apple') else if(variableEnum === Fruit.Orange) console.log('Orange') else if(variableEnum === Fruit.Melon) console.log('Melon') else console.log('None')
-
注释为枚举值类型:
let variableA: Fruit.Apple variableA = 0
1.3.2 tuple
-
元组确定性:
元组确定了数组每个元素的类型以及数组的长度。数组只能规定每个元素的类型,不能确定数组长度。
const tuple: [number, number] = [1, 2] const ar: Array<number> = [1, 2] const add = (a: number, b: number) => a + b add(...tuple) // 合法,元组长度固定,展开后add接收两个参数 add(...ar) // 不合法,数组长度不确定,展开后接收未知个参数
-
元组可选参数:
在进行元组类型的注释时可以使用?表示元素可以不存在,但是只能在最后使用。
type AnyTypeArray = [number, boolean?, string?] const ar: AnyTypeArray = [1] // 合法 const ar: AnyTypeArray = [1, true] // 合法
如果没有使用元组的可选元素,那么默认为undefined。元组的长度会包括类型注释时的可选参数。
type AddParameters = [number, number, number?] const ar: AddParameters = [1, 2] const add = (a: number, b: number) => a + b add(...ar) // 报错,元组长度实际为3
-
元组剩余参数:
在进行元组类型注释时可以在非尾部使用剩余参数。
type Tuple = [number, ...boolean[], string] const ar: Tuple = [1, 'hello'] const arr: Tuple = [1, true, false, 'hello']
☆☆这种做法还可以实现JavaScript无法做到的函数形参声明。相当于可以在非末尾声明剩余形参。
function handleUserInfo(...args: [number, ...string[], boolean]) { const [age, name, gender, location, isMarried] = args console.log(age, name, gender, location, isMarried) } // 使用效果相当炸裂 handleUserInfo(21, 'Danny', 'male', 'WeiHai', true)
-
只读元组:
只读元组可以使用TypeScript内置的Readonly别名来声明
const tuple: Readonly<[number, string]> = [21, 'Danny'] const [userAge, userName] = tuple
只读元组可以使用readonly关键字来声明
const tuple: readonly [number, string] = [21, 'Danny'] const [userAge, userName] = tuple
1.3.3 type
1.3.3.1 描述类型别名
使用类型别名指定已经存在的类型
// 使用类型别名指定为对象字面量类型
type School = {
name: String,
age: Number
}
let variable: School
// 使用类型别名指定为TypeScript基本类型
type str = string
1.3.3.2 扩展类型别名
类型别名一旦声明就只能通过&运算符进行扩展
type A = {
name: string
}
type B = {
age: number
}
type C = A & B // 合法
B.name: string // 非法
1.3.4 联合类型和交叉类型
1.3.4.1 联合类型
联合类型指的是用|来表示变量可以取不同的类型
let variableA: string | number | boolean
1.3.4.2 交叉类型
交叉类型指的是用&来表示变量取多种类型中的交集。
-
对于接口,对象字面量类型:
使用&交叉会合并属性,因为可以做到同时满足。
interface A { name: string } interface B { gender: string } type C = A & B let obj: C = { name: 'Danny', gender: 'male' }
-
对于普通类型:
使用&交叉会直接取交集,因为无法同时满足。
type TypeA = string | number type TypeB = number | boolean type TypeC = TypeA & TypeB let variable: TypeC = 123 // variable只能取数值类型
2.类型推断
合理利用类型推断可以使代码更简洁。不过要注意,尽可能不要被推断为any,在typescript严格模式下是不允许的。
2.1 类型推断方式
2.1.1 根据已有类型注释进行类型推断
下面函数的返回值一定是number类型,这是根据已有类型注释进行推断。
下面对象字面量实现接口,因为接口中已经显示指定类型且对象指定为接口的实现,那么实现的函数的参数将会被自动进行类型推断。
function cal(a: number, b: number) {
return a + b
}
interface Test {
work(a: number, b: number): void
}
const TestMaker: Test = {
work: (a, b) => {}
}
2.1.2 根据变量值进行类型推断
下面会根据“hello”推测str为字符串类型。注意:除非显式指定变量为JavaScript值类型,否则不会被推测为JavaScript值类型。即下面的str不会被推测为“hello”类型。
let str = "hello"
2.2 类型推断特例
2.2.1 不进行类型推断
-
类使用implements实现接口:
例如使用类来实现一个接口中的check方法。注意:(tsc 5.1.3)由于没有类型注释,编译器无法进行类型推断。在实现接口方法时会认为参数均为any类型。
interface CheckAble { check(name: string): boolean } class NameChecker implements CheckAble { check(s) { return true } } let nc = new NameChecker nc.check(123) // 编译不会报错
添加类型注释,更规范地实现接口。(1)可以在声明变量时添加类型注释,指明是实现接口的变量(2)可以在类实现check方法时对参数进行类型注释
interface CheckAble { check(name: string): boolean } class NameChecker implements CheckAble { check(s) { return true } } let nc: CheckAble = new NameChecker nc.check(123) // 编译会报错
-
回调函数传参:
该内容会在“TypeScript函数”博文中详细介绍,指的是回调函数实参个数可以少于形参个数。
const filter = <T>(ar: Array<T>, callback: (el: T, idx: number, arr: Array<T>) => boolean): Array<T> => ar.filter(callback) console.log(filter([1, 2, 3, 4], el => !!(el % 2))) // 只给callback传了第一个参数,少于形参要求的三个参数
2.2.2 数组推断
-
字面量创建数组将被推测为数组:
在不进行类型注释时ar不会被推断为元组
const ar = [1, 2, 3]
-
数组长度推断:
在对数组进行推断时,默认认为数组类型大小是不固定的(参考“TypeScript对象”博文)。这也是为什么我们特别地提出元组中元素个数是确定的的原因。
const add = (a: number, b: number) => a + b const ar = [1, 2] console.log(add(...ar)) // 报错,推测ar为number数组类型且个数不确定,展开后不是确定的两个元素,与函数形参要求不匹配。 const ar = [1, 2] as const console.log(add(...ar)) // 合法,使用类型断言将数组推断为元组,此时数组元素个数确定
2.2.3 值类型推断
值类型推断优先度最低,如果不显示地进行类型注释,则一定不会被推断为值类型
let str = "hello" // 推断为string类型而不是“hello”类型
let str2: "hello" = "hello" // 显示指定str2为“hello”类型
3.类型断言
类型断言用在表达式之后,是一种显示类型转化的方法
3.1 类型推断断言
-
类型注释优先级大于类型推断断言:
一旦进行了类型注释,再进行类型断言不会影响变量最终类型的推理。下面的例子虽然断言num是any,但是已经确认了num是number类型,所以最终num是number类型,访问length属性是非法的。
const num: number = 123 as any num.length // 报错,num是number类型
下面的例子没有进行类型注释,此时使用类型断言会影响类型推断过程的走向。此时num被推测为any类型,访问length合法。
const num = 123 as any num.length // 合法
☆☆下面的例子很有代表性,默认字面声明数组类型会被推断为数组,但是会认为数组长度未知,在使用展开符展开后作为实参传给函数,函数会认为你传递了未知数量的参数。这时候就需要类型断言,使用特殊的类型断言
as const
说明对象是只读的,因此每个元素的类型固定下来,长度也固定下来,这时候数组会被推断为元组。这样就可以展开后作为剩余实参传给函数。const ar = [1, 2] as const // as断言使得ar被推断为元组 const add = (a: number, b: number) => a + b add(...ar) // 合法
☆☆下面的例子也有代表性。在理解了上面的例子后可以知道,一旦进行了类型注释,那么son就是Father类型。
interface Father { name: string, age: number } interface Son extends Father { gender: string, getInfo: () => void } const son: Father = { name: 'Danny', age: 21 } as Son son.getInfo() // 报错,son是Father类型,没有getInfo方法 son.gender // 报错,son是Father类型,没有gender属性
-
类型推断断言不能用于无交集的类型:
类型断言可以将123推断为any,也可以将any推断为123。即可以在具象和非具象之间断言,但是不能在无关类型间断言。
下面的例子说明了字符串不可能被推断为数值。
const variable = 'hello' as number // 报错,我们希望将variable推断为number类型,但是这一定不可能发生
下面的例子说明了数组不可能被推断为元组。
const ar: Array<number> = [1, 2] as const // 报错,我们希望将ar推断为元组,但是已经指定了ar为数组,数组类型长度在TypeScript中被认为是不确定的,所以ar一定不可能被推断为元组 const arr: ReadonlyArray<number> = [1, 2] as const // 报错,理由同上(如果不了解as的机理,这种结果对于初学者可能难以理解)
-
显示类型转化:
除去JavaScript中的显示类型转化,TypeScript中显示类型转化有两种:(1)使用类型断言(2)在变量前使用<>改变其类型
const ar = [1, 2] as const const ar = <const>[1, 2] const ar: ReadonlyArray<number> = [1, 2, 3] const arr: Array<number> = [4, 5, 6]
3.2 非空赋值断言
-
类型注释优先级大于非空赋值断言:
还是和类型推断断言as一样,类型注释一旦确定,我们无法再影响类型推断
const str: null = null ! console.log(str.length) // 报错,// 我们希望str被推断为非空,但是类型注释已经说明str为空,因此str一定不会被推断为非空
下面这个例子说明非空赋值断言的应用场景
const str = Math.random() + 0.5 > 0.5 ? 'hello' : null ! console.log(str.length) // 合法,Math.random() + 0.5一定大于0.5,我们希望str被推断为非空。否则str会被推断为联合类型
下面这个例子说明了非空赋值断言可以被类型推断断言替代
const str = (Math.random() + 0.5 > 0.5 ? 'hello' : null) as string console.log(str.length) // 合法
4.类型操作符
4.1 keyof操作符
keyof操作符用于获取对象的键的类型
-
keyof用法:
keyof操作符用于操作一个类型,返回该类型的所有键对应的类型。
interface User { name: string, age: number } type UserKey = keyof User const user: UserKey = 'name' // 合法 const user2: UserKey = 'age' // 合法 const user3: UserKey = '123' // 非法,UserKey的类型就是"name" | "age",其它任何值都不能取
-
keyof操作对象:
注意类型操作符keyof操作的是类型,不能操作值
const obj = { name: "Danny", age: 21 } type ObjAlias = keyof obj // 报错,obj是值不是类型
-
keyof操作结果:
因为对象的键是确定的,因此操作结果只可能是“数值类型”或“字符串值类型”或“符号值类型”。
const alias = Symbol('alias') interface User { name: string, age: number, [alias]: symbol, [index: number]: string } type Type = keyof User // 操作结果是符号,是值类型Symbol('alias') const user: Type = alias // 操作结果是字符串,是值类型“name” const user2: Type = "name" // 操作结果是number const user3: Type = 123
4.2 typeof操作符
typeof操作符用于获取值的类型
-
typeof用法:
typeof操作符与keyof操作符恰好相反,typeof操作符作用于值类型。
在TypeScript中修改了typeof操作符的功能,不再是像JavaScript中只返回字符串,而是会返回类型。
const num: number = 123 type Num = typeof num const myNum: Num = 456 // typeof作用于对象不会返回object或Object const user = { name: 'Danny', age: 21, gender: 'male' } type User = typeof user const myUser: User = { name: 'Cathy', age: 21, gender: 'male' }
4.1 索引签名
索引签名指定了对象的键和值分别应该为什么类型
-
键只能指定为数值或字符串类型:
interface User { [index: number]: string // 合法 } interface User { [index: string]: any // 合法 } interface User { [index: symbol]: string // 非法,索引签名中键的类型只能被指定为number或string }
-
索引签名引起的冲突:
-
索引签名只应存在一个:
interface User { [index: number]: string // 合法 [index: string]: string // 合法,但没有意义。number键名会默认转化为string类型 } interface User { [index: number]: string // 合法 [index: string]: number // 报错,索引签名不能同时满足值既是string又是number }
-
索引签名和已有属性冲突:
interface User { age: number, // 报错,下面的索引签名要求了所有键对应的值都应该为string类型 [index: string]: string }
-
4.4 索引访问操作符
索引访问操作符用于获取对象值的类型
-
索引访问操作符用法:
索引访问操作符是[],其中接收某一种类型。通过这种类型去对象(接口或别名)中寻找其对应的值。
这里容易理解错误的是形如
User["name"]
,你可能认为这里的“name”只是字符串,但是实际上它是“name”值类型。这也是解释了为什么在处理符号类型的键时要使用typeof alias
的原因,因为[]操作符中只能接收类型而不是值。const alias = Symbol.for('alias') interface User { name: string, age: number, [alias]: boolean, } type UserAliasType = User[typeof alias] // 注意这里不是alias,是symbol.for('alias')类型,因为[]接收类型而不是值 type UserNameType = User["name"] // 这里的“name”不是字符串,而是“name”值类型 type UserAgeType = User["age"] // 这里的“age”同理也是“age”值类型 const myAlias: UserAliasType = true const myName: UserNameType = 'Danny' const myAge: UserAgeType = 21
-
索引访问操作符访问索引签名:
访问索引签名键对应的值时,可以通过number,string或具体的number值类型来进行访问
// 设定索引键为string interface User { name: string [index: string]: string } type UserSignType = User[string] // 合法,通过string类型访问 const mySign: UserSignType = 'hello' // 设定索引键为number interface User { name: string [index: number]: string } type UserSignType = User[number] // 合法,通过number类型访问 type UserDetailType = User[1] // 合法,通过number值类型访问 const mySign: UserDetailType = 'hello'
复杂的类型提取示例(源自TypeScript官网)
const MyArray = [ { name: "Alice", age: 15 }, { name: "Bob", age: 23 }, { name: "Eve", age: 38 }, ]; type Age = typeof MyArray[number]["age"]; // 先通过typeof得到Array类型,再通过[number]访问索引类型得到{ name: string, age: number}类型,再通过["age"]索引访问得到number类型