likes
comments
collection
share

TypeScrip类型编程第一步:从索引类型到映射类型

作者站长头像
站长
· 阅读数 4

学过java后学TS一天就感觉学会了,一看就会 一做就废。B站上的绝大部分TS还不如去看看文档 一点类型编程的进阶知识都没有。建议有编程基础的直接看文档。

索引类型

索引类型:索引签名类型索引类型查询索引类型访问

索引签名类型

索引签名类型主要指的是在接口或类型别名中,通过以下语法来快速声明一个键值类型一致的类型结构

interface MyObject {
  [key: string]: number;
}

const obj: MyObject = {
  a: 1,
  b: 2,
  c: 3,
};

//[]是索引类型访问 具体详情下面小节有
console.log(obj['a']); // 输出: 1  
console.log(obj['b']); // 输出: 2

定义了一个名为MyObject的接口,它具有一个索引签名类型,允许使用字符串作为索引,并且对应的值类型为数字。然后,我们创建了一个obj对象,可以通过字符串索引来访问其属性。

interface VS 索引签名


直接使用接口来定义对象类型是TypeScript中最常见的情况,特别是当你知道对象将具有哪些属性以及它们的类型时。然而,有些情况下,对象的确切属性名事先未知或者对象的属性名是动态的,但它们的值类型是已知的。在这些情况下,索引签名就非常有用。

不同的属性名,定义不同的类型


// 错误的尝试: TypeScript不允许这样做
interface MixedTypeDictionary {
  [key: string]: string;
  [key: number]: number; // Error: An index signature parameter type cannot be a union type
}

需要不同的属性具有不同的类型,你应该明确地在接口或类型别名中声明它们:


interface StringOrBooleanTypes {
  propA: number;
  propB: boolean;
  [key: string]: number | boolean;
}

索引类型查询

将对象中的所有键转换为对应字面量类型,然后再组合成联合类型

interface Foo {
  gsy: 1,
  599: 2
}

type FooKeys = keyof Foo; // "gsy" | 599

索引类型访问

通过 obj[expression] 的方式来访问属性

interface Foo {
  propA: number;
  propB: boolean;
}

type PropAType = Foo['propA']; // number
type PropBType = Foo['propB']; // boolean

配合keyof

interface Foo {
  propA: number;
  propB: boolean;
  propC: string;
}

type PropTypeUnion = Foo[keyof Foo]; // string | number | boolean

在未声明索引签名类型的情况下,我们不能使用 NumberRecord[string] 这种原始类型的访问方式,而只能通过键名的字面量类型来进行访问。

interface Foo {
  propA: number;
}

// 类型“Foo”没有匹配的类型“string”的索引签名。
type PropAType = Foo[string]; 

★映射类型 ★

前置知识学完了,就到三合一了。

映射类型的主要作用即是基于键名映射到键值类型。放段小册的伪代码。

type Stringify<T> = {
  [K in keyof T]: string;
};

//伪代码
const StringifiedFoo = {};
for (const k of Object.keys(Foo)){
  StringifiedFoo[k] = string;
}

xdm看了上面的代码 有点基础肯定学会了,我也学会了 但过会又忘了。还得再细点,来点正确的废话

我的废话

type Clone<T> = {
  [K in keyof T]: T[K];
};

这里的T[K]其实就是上面说到的索引类型访问,我们使用键的字面量类型访问到了键值的类型,这里就相当于克隆了一个接口。需要注意的是,这里其实只有K in 属于映射类型的语法,keyof T 属于 keyof 操作符,[K in keyof T][]属于索引签名类型,T[K]属于索引类型访问。

  • T 是一个将被克隆的类型变量。
  • [K in keyof T] 是一个索引类型查询和映射类型的结合。对于类型 T 中的每个属性键 K,都会创建一个相同键的属性。
  • T[K] 表示键 K 在原始类型 T 中对应的属性类型。
type User = {
  id: number;
  name: string;
  email: string;
};

type UserClone = Clone<User>;

定义了一个名为 User 的类型,它有三个属性:idname、和 email。当我们使用映射类型 [K in keyof T] 时,这里的 T 是指我们定义的 User 类型,keyof TUser 的所有属性键的联合类型,K 是这些键中的每一个。 对于 User 类型,keyof User 将得到所有属性名的联合类型

keyof User = "id" | "name" | "email";

每个 K 将相继地是这个联合类型的成员,即先是 "id",然后 "name",最后 "email"

对于 T[K],它表示类型 T 中键 K 对应的值的类型。在我们的例子中,User[K] 的可能值为以下类型:

  • 对于 K 为 "id"User[K] 的类型是 number
  • 对于 K 为 "name"User[K] 的类型是 string
  • 对于 K 为 "email"User[K] 的类型是 string

Clone<User> 的上下文中,[K in keyof User]: User[K]; 将分别为每个属性创建一个同名的属性,并且保持对应的类型,如下所示:

  • id: number,因为 User["id"] 是 number 类型。
  • name: string,因为 User["name"] 是 string 类型。
  • email: string,因为 User["email"] 是 string 类型。 因此,映射类型 [K in keyof T]: T[K]; 实际上遍历了 User 类型的每个属性,保留了每个属性的键和类型,并在新的 Clone<User> 类型中复制了这个结构。

简单应用

假设我们有一个用户对象的类型:

type User = {
  id: number;
  name: string;
  email: string;
};

我们想要创建一个这个用户对象的克隆,但是不同之处是我们希望在某处安全地使用它,不能引用原始的 User 类型,以便它可以单独修改。

我们可以创建一个克隆类型:

type UserClone = Clone<User>;

这里的 UserClone 将会跟 User 有完全相同的属性,但是它是一个独立的类型。我们可以对 UserClone 做一些修改,比如添加或修改属性,而不会影响到原来的 User 类型:

interface UserClone extends Clone<User> {
  avatarUrl?: string; // 可选属性
}

function updateUserAvatar(user: UserClone, url: string): UserClone {
  // 在这个函数内,我们处理的是 UserClone 类型,它是从 User 类型克隆而来的,即使我们给 UserClone 添加了额外的属性。
  user.avatarUrl = url;
  return user;
}

// 在这里,即使我们调用 `updateUserAvatar`,原始的 User 类型也不会被添加 avatarUrl 属性。

通过克隆类型,我们可以保证类型安全,同时保持类型的灵活性和可维护性。