Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TypeScript 之映射类型 #230

Open
mqyqingfeng opened this issue Dec 6, 2021 · 4 comments
Open

TypeScript 之映射类型 #230

mqyqingfeng opened this issue Dec 6, 2021 · 4 comments

Comments

@mqyqingfeng
Copy link
Owner

mqyqingfeng commented Dec 6, 2021

前言

TypeScript 的官方文档早已更新,但我能找到的中文文档都还停留在比较老的版本。所以对其中新增以及修订较多的章节进行了翻译整理。

我同时搭建了 TypeScript 中文站点: https://ts.yayujs.com ,正是因为我亲自翻译过,所以我认为这是国内最好的系统学习 TS 的教程之一。

本篇翻译整理自 TypeScript Handbook 中 「Mapped Types」 章节。

欢迎围观朋友圈、加入低调务实优秀中国好青年前端社群,一个人走得快,一群人走得远。

映射类型(Mapped Types)

有的时候,一个类型需要基于另外一个类型,但是你又不想拷贝一份,这个时候可以考虑使用映射类型。

映射类型建立在索引签名的语法上,我们先回顾下索引签名:

// 当你需要提前声明属性的类型时
type OnlyBoolsAndHorses = {
  [key: string]: boolean | Horse;
};
 
const conforms: OnlyBoolsAndHorses = {
  del: true,
  rodney: false,
};

而映射类型,就是使用了 PropertyKeys 联合类型的泛型,其中 PropertyKeys 多是通过 keyof 创建,然后循环遍历键名创建一个类型:

type OptionsFlags<Type> = {
  [Property in keyof Type]: boolean;
};

在这个例子中,OptionsFlags 会遍历 Type 所有的属性,然后设置为布尔类型。

type FeatureFlags = {
  darkMode: () => void;
  newUserProfile: () => void;
};
 
type FeatureOptions = OptionsFlags<FeatureFlags>;
// type FeatureOptions = {
//    darkMode: boolean;
//    newUserProfile: boolean;
// }

映射修饰符(Mapping Modifiers)

在使用映射类型时,有两个额外的修饰符可能会用到,一个是 readonly,用于设置属性只读,一个是 ? ,用于设置属性可选。

你可以通过前缀 - 或者 + 删除或者添加这些修饰符,如果没有写前缀,相当于使用了 + 前缀。

// 删除属性中的只读属性
type CreateMutable<Type> = {
  -readonly [Property in keyof Type]: Type[Property];
};
 
type LockedAccount = {
  readonly id: string;
  readonly name: string;
};
 
type UnlockedAccount = CreateMutable<LockedAccount>;

// type UnlockedAccount = {
//    id: string;
//    name: string;
// }
// 删除属性中的可选属性
type Concrete<Type> = {
  [Property in keyof Type]-?: Type[Property];
};
 
type MaybeUser = {
  id: string;
  name?: string;
  age?: number;
};
 
type User = Concrete<MaybeUser>;
// type User = {
//    id: string;
//    name: string;
//    age: number;
// }

通过 as 实现键名重新映射(Key Remapping via as

在 TypeScript 4.1 及以后,你可以在映射类型中使用 as 语句实现键名重新映射:

type MappedTypeWithNewProperties<Type> = {
    [Properties in keyof Type as NewKeyType]: Type[Properties]
}

举个例子,你可以利用「模板字面量类型」,基于之前的属性名创建一个新属性名:

type Getters<Type> = {
    [Property in keyof Type as `get${Capitalize<string & Property>}`]: () => Type[Property]
};
 
interface Person {
    name: string;
    age: number;
    location: string;
}
 
type LazyPerson = Getters<Person>;

// type LazyPerson = {
//    getName: () => string;
//    getAge: () => number;
//    getLocation: () => string;
// }

你也可以利用条件类型返回一个 never 从而过滤掉某些属性:

// Remove the 'kind' property
type RemoveKindField<Type> = {
    [Property in keyof Type as Exclude<Property, "kind">]: Type[Property]
};
 
interface Circle {
    kind: "circle";
    radius: number;
}
 
type KindlessCircle = RemoveKindField<Circle>;

// type KindlessCircle = {
//    radius: number;
// }

你还可以遍历任何联合类型,不仅仅是 string | number | symbol 这种联合类型,可以是任何类型的联合:

type EventConfig<Events extends { kind: string }> = {
    [E in Events as E["kind"]]: (event: E) => void;
}
 
type SquareEvent = { kind: "square", x: number, y: number };
type CircleEvent = { kind: "circle", radius: number };
 
type Config = EventConfig<SquareEvent | CircleEvent>
// type Config = {
//    square: (event: SquareEvent) => void;
//    circle: (event: CircleEvent) => void;
// }

深入探索(Further Exploration)

映射类型也可以跟其他的功能搭配使用,举个例子,这是一个使用条件类型的映射类型,会根据对象是否有 pii 属性返回 true 或者 false :

type ExtractPII<Type> = {
  [Property in keyof Type]: Type[Property] extends { pii: true } ? true : false;
};
 
type DBFields = {
  id: { format: "incrementing" };
  name: { type: string; pii: true };
};
 
type ObjectsNeedingGDPRDeletion = ExtractPII<DBFields>;
// type ObjectsNeedingGDPRDeletion = {
//    id: false;
//    name: true;
// }

TypeScript 系列

TypeScript 系列文章由官方文档翻译、重难点解析、实战技巧三个部分组成,涵盖入门、进阶、实战,旨在为你提供一个系统学习 TS 的教程,该系列预计 40 篇左右。

本篇已收录在掘金专栏 《TypeScript 系列》

此外我还写过 JavaScript 系列React 系列Next.js 系列博客搭建系列冴羽答读者问等 14 个系列文章, 全系列文章目录:https://github.com/mqyqingfeng/Blog

通过文字建立交流本身就是一种缘分,欢迎围观我的“朋友圈”、加入“低调务实优秀中国好青年”前端社群,分享技术,带你成长。

@lisongyu
Copy link

lisongyu commented Apr 6, 2022

// 当你需要提前声明属性的类型时
type OnlyBoolsAndHorses = {
[key: string]: boolean | Horse;
};

const conforms: OnlyBoolsAndHorses = {
del: true,
rodney: false,
};
中Horse声明,是否要去掉

@xiterjia
Copy link

xiterjia commented May 5, 2022

会根据对象是否有 pii 属性返回 true 或者 false

{ pii: true } 改为 { pii: boolean } 语义是不是更好

type ExtractPII<Type> = {
  [Property in keyof Type]: Type[Property] extends { pii: boolean } ? true : false;
};

type DBFields = {
  id: { format: "incrementing" };
  name: { type: string; pii: true };
  comment: { type: string; pii: false };
};
 
type ObjectsNeedingGDPRDeletion = ExtractPII<DBFields>;
// type ObjectsNeedingGDPRDeletion = {
//    id: false;
//    name: true;
//    comment: true;
// }

@sadJohn
Copy link

sadJohn commented May 23, 2022

@xiterjia

会根据对象是否有 pii 属性返回 true 或者 false

{ pii: true } 改为 { pii: boolean } 语义是不是更好

type ExtractPII<Type> = {
  [Property in keyof Type]: Type[Property] extends { pii: boolean } ? true : false;
};

type DBFields = {
  id: { format: "incrementing" };
  name: { type: string; pii: true };
  comment: { type: string; pii: false };
};
 
type ObjectsNeedingGDPRDeletion = ExtractPII<DBFields>;
// type ObjectsNeedingGDPRDeletion = {
//    id: false;
//    name: true;
//    comment: true;
// }

翻译错了,原文是根据pii是否设置为true返回true false

@YuFengjie97
Copy link

YuFengjie97 commented Oct 1, 2022

typescript面向类型编程

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants