我如何提高 Typescript 技能 #2:映射类型
我将和大家分享一些提升 Typescript 技能的技巧!今天我们将学习如何使用Mapped Type!
什么是映射类型以及为什么使用映射类型?
在以下情况下使用mapped type很有用:
需要从另一个类型创建派生类型并保持两者同步
让我们看一个例子来理解!
type User = {
  name: string
  age: number
  userName: string
}
type UserPermissions = {
  canUpdateName: boolean
  canUpdateAge: boolean
  canUpdateUserName: boolean
}
User这里我们可以看到&类型之间有相似之处UserPermissions。有什么解决方案可以将这两种类型联系起来吗?因为我们不想UserPermissions每次在类型中添加新属性时都手动添加属性User。
答案是yes,解决方案是Mapped type!
映射类型:基础知识
创建相同类型
在回答上述问题之前,让我们看看映射类型是如何工作的!
我们将从User类型创建一个类型,首先让我们创建完全相同的类型。
type User = {
  name: string
  age: number
  userName: string
}
type UserCopy = {
  [Property in keyof User]: User[Property]
}
如果你检查Usercopy类型你会看到这个
type UserCopy = {
    name: string;
    age: number;
    userName: string;
}
但是我们为什么要这样做,我们创建与User?!相同的类型。
我选了一个简单的例子来演示Mapped 类型的工作原理。我会尽量解释清楚到底是怎么回事。
type UserCopy = {
  [Property in keyof User]: User[Property]
}
首先,我们使用运算符迭代User类型中的每个键(使用映射类型时经常使用),每个键都包含在变量中(,,)in keyofProperty'name''age''userName'
之后,我们为每个属性赋值,并将 中的值赋给它们User[Property]。直观地看,我们会得到类似这样的效果:
1)开始:[Property in keyof User]: User[Property]
- 
  迭代到 User 的第一个属性: [name]: User['name'],因此我们得到以下输入name: string
- 
  迭代到 User 的第二个属性: [age]: User['age'],因此我们得到以下输入age: number
- 
  迭代到 User 的最后一个属性: [userName]: User['userName'],因此我们得到以下输入userName: string
User2)迭代每个键之后,我们得到一个与!类型相同的新类型。
编辑不同类型的值
我们还可以编辑属性值类型,例如:
type UserCopy = {
  [Property in keyof User]: boolean
}
并得到类似
type UserCopy = {
    name: boolean;
    age: boolean;
    userName: boolean;
}
我们开始见识Mapped 类型的威力了!如果我们在类型中添加一个新属性User,它将自动添加到该UserCopy类型中,无需执行任何操作!别忘了,开发人员很懒 ;)
映射类型:映射修饰符
从现在开始玩 Mapped 类型吧!
我们将使用mapping modifier映射类型。
如果我需要创建以下类型UserReadonlyProperty,即每个键上都User带有readonly标志的类型。我可以创建类似的东西。
type User = {
  name: string
  age: number
  userName: string
}
type UserCopy = {
  readonly [Property in keyof User]: User[Property];
};
// type UserCopy = {
    // readonly name: string;
    // readonly age: number;
    // readonly userName: string;
// }
哇,真有意思!我们直接readonly给每个键分配了标志!我们也可以反其道而行之!只需要-在 readonly 之前使用,就能从类型中删除所有 readonly 标志了~
type User = {
  readonly name: string
  readonly age: number
  readonly userName: string
}
type UserCopy = {
  -readonly [Property in keyof User]: User[Property];
};
// We Get this 👇
type UserCopy = {
    name: string;
    age: number;
    userName: string;
}
这就是所谓的mapping modifier,我们还有另一个映射修饰符需要向您展示,即Optional映射修饰符(?:)。
type User = {
  name: string
  age: number
  userName: string
}
type UserCopy = {
  [Property in keyof User]?: User[Property];
};
// We Get this 👇
type UserCopy = {
    name?: string | undefined;
    age?: number | undefined;
    userName?: string | undefined;
}
如您所见,我们可以在键上添加“readonly”和“optionnal”标志,但我们也可以删除它们!通过使用(-)标志:[Property in keyof User]-?: User[Property]
type User = {
  name?: string
  age?: number
  userName: string
}
type UserCopy = {
  [Property in keyof User]-?: User[Property];
};
// We Get this 👇
type UserCopy = {
    name: string;
    age: number;
    userName: string;
}
最后我们可以将两者结合起来🔥
type User = {
  name: string
  age: number
  userName: string
}
type UserCopy = {
  readonly [Property in keyof User]?: User[Property];
};
// We Get this 👇
type UserCopy = {
    readonly name?: string | undefined;
    readonly age?: number | undefined;
    readonly userName?: string | undefined;
}
 映射类型:键重新映射as
我们看到了mapping modifier,这是一个与映射类型结合的非常好的特性!现在让我们更深入地了解一下mapped type
文字字符串
我们可以使用文字字符串重新映射键!
type User = {
  name: string
  age: number
  userName: string
}
type RenameKey<Type> = {
  [Property in keyof Type as `canUpdate${string & Property}`]: Type[Property]
}
type UserCopy = RenameKey<User>
// We Get this 👇
type UserCopy = {
    canUpdatename: string;
    canUpdateage: number;
    canUpdateuserName: string;
}
使用as许可证来更改密钥值并将其保留在新值中!
让我们一步一步地解构合成。
1)我们遍历Type键,因此我们得到name,age和userName。
2)在每次迭代中,我们将键值重命名为canUpdate${Property},因此我们得到
- canUpdatename
- canUpdateage
- canUpdateuserName
2.5) 如果我们仔细观察 Syntax,就能看到{string & Property}Syntax。为什么这里需要使用stringValue 呢?我会写一篇专门的文章来解释这个问题,但总结一下,我们需要告诉 TS 键是 astring而不是 a symbol,因为我们不能在字面量中使用symbol字符串(其他键类型,例如数字或 bingint,则需要使用字符串)。
如你所见,与映射类型结合使用时,重新映射键的功能非常强大。但这里有一个“小”问题,我们得到了canUpdatenamenot canUpdateName。
我们怎样才能实现这个目标?🧐
内在字符串
为了帮助字符串操作,TypeScript 包含一组可用于字符串操作的类型,我们现在将使用它来转换canUpdatename为canUpdateName。
type User = {
  name: string
  age: number
  userName: string
}
type Copy<Type> = {
  [Property in keyof Type as `canUpdate${Capitalize<string & Property>}`]: Type[Property];
};
type UserCopy = Copy<User>
// We Get this 👇
type UserCopy = {
    canUpdateName: string;
    canUpdateAge: number;
    canUpdateUserName: string;
}
我们做到了!使用intrinsic string允许我们将字符串值大写!这里我们将 type 中的 Key 值大写User。
我们还可以使用intrinsic string映射类型上下文。
type LowercaseGreeting = "hello, world"
type Greeting = Capitalize<LowercaseGreeting> //Hello, world
这里还有其他的内在字符串
类型实用程序
我再重复一遍,但是Mapped type功能强大,我们可以重命名一种类型的每个键来创建另一种类型,我们可以用它intrinsic string来操作字符串。
还有其他事情要与他们有关;)
我们可以使用 Type Utils!(如果你不知道这是什么,请查看我上一篇关于 Typescript 技巧的文章,或者点击此处)
如果我们需要从类型中排除一组键来创建转换类型,我们可以结合使用mapped typeType Utils !!
type User = {
  name: string
  age: number
  userName: string
}
type CopyWithoutKeys<Type, Keys> = {
  [Property in keyof Type as Exclude<Property , Keys>]: Type[Property];
};
type UserCopyWithoutNameAndUsername = CopyWithoutKeys<User, 'name' | 'userName'>
// We Get this 👇
type UserCopyWithoutNameAndUsername = {
    age: number;
}
我想你已经理解了 Mapped 类型的语法和概念。当然,我们可以使用之前见过的所有功能!
奖励:从映射类型中获取所有值类型
对于读到这一节的所有人而言,这都是一个奖励。
您可以从映射类型(和类型)中获取所有键的值类型
type User = {
  name: string
  age: number
  userName: string
  isAdmin: boolean
}
type CopyWithoutKeys<Type> = {
  [Property in keyof Type]: Type[Property];
};
type UserCopy = CopyWithoutKeys<User>
type UserCopyValueTypes = UserCopy[keyof UserCopy] // string | number | boolean
请注意,我添加了isAdminkey 而没有在UserCopytype 中添加它,这就是神奇之处Mapped Type;)
映射类型的高级概念
永远不会作为键/对中的值
让我们看看当您使用 Never 作为映射类型的键/值时会发生什么!
Never 作为映射类型的键
如果在使用映射类型进行案例时在键迭代期间添加条件never,它将产生一些很酷的东西
type isA<T> = T extends 'a' ? never : T
type To<T> = {
    [P in keyof T as isA<P>]: T[P]
}
type Toto = To<{ a: 1; b: 2; c: 3 }> // { b: 2; c: 3; }
当您设置一个带有值的键时never,它将从最终类型中被省略!
Never 作为映射类型的值
正如我们之前所见,使用never可以产生一些关于映射类型的有用技巧,但是如果您将其用作never值,会发生什么?
type To<T> = {
    [P in keyof T]: T[P] extends never ? never : P
}
type Toto = To<{ a: 1; b: 2; c: never }> // {a: "a" b: "b" c: never;}
正如您所见,never值并不Omit像never键。
使用never值可以根据条件删除某些键/值,因此如果您需要这样做,请在key级别使用您的条件!
// type PropertyKey = string | number | symbol 
type isA<T extends Record<PropertyKey, unknown>, P extends PropertyKey> = T[P] extends 1 // Your condition
  ? never : P
type To<T extends Record<PropertyKey, unknown>> = {
    [P in keyof T as isA<T,P>]: T[P]
}
type Toto = To<{ a: 1; b: 2; c: 2 }> // {b: 2;c: 2;}
在上面的例子中,我们删除所有值为 1 的键/值,我们在级别添加条件key,因此never值将自动Omit!
具有值的联合类型
如果您对此类错误有任何问题
type Colors = "red" | "green" | "blue";
type RGB = [red: number, green: number, blue: number];
const palette: Record<Colors, string | RGB> = {
    red: [255, 0, 0],
    green: "#00ff00",
    blue: [0, 0, 255]
};
const redComponent = palette.red.at(0); // Property 'at' does not exist on type 'string | RGB'.
你应该阅读我上一篇关于satisfies运算符的文章!
我希望你喜欢这篇文章!
☕️ 你可以支持我的作品🙏
🏃♂️ 你可以关注我 👇
🕊 推特:https://twitter.com/code__oz
👨💻 Github:https://github.com/Code-Oz
🇫🇷🥖 对于法国开发者,你可以查看我的YoutubeChannel
你也可以标记🔖这篇文章!
文章来源:https://dev.to/codeoz/how-i-improve-my-skills-in-typescript-2-mapped-type-dag 后端开发教程 - Java、Spring Boot 实战 - msg200.com
            后端开发教程 - Java、Spring Boot 实战 - msg200.com