首页 前端知识 TypeScript类型注释

TypeScript类型注释

2024-09-28 23:09:42 前端知识 前端哥 861 93 我要收藏

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.AppleFruit[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类型
    复制
转载请注明出处或者链接地址:https://www.qianduange.cn//article/18694.html
标签
评论
还可以输入200
共0条数据,当前/页
发布的文章

安装Nodejs后,npm无法使用

2024-11-30 11:11:38

大家推荐的文章
会员中心 联系我 留言建议 回顶部
复制成功!