ts 修饰符_TS的高级类型编程

本文介绍了 TypeScript 中的高级类型,包括索引类型、映射类型、条件类型及其应用场景,如IfEquals、MutableKeys、ReadonlyKeys等。详细解释了如何利用这些类型进行更复杂的类型操作,例如从对象中选取特定属性、改变属性的可选性等,并通过阅读utility-types库的源码来深入理解这些概念。
摘要由CSDN通过智能技术生成

转自:https://juejin.cn/post/6898270313050537997

1.索引类型

keyof 索引类型查询操作符,可以获取泛型T上所有的 public 属性名构成联合类型

class Person {
    name: string = "胡先生"
    age: number = 18
    private UserId: number = 123
}

type PropNames = keyof Person
da5181baee6fade5d76c2f6dc1bfd284.png

注意:

  1. keyof只能获取泛型上的 public 属性名,属性名为字符串类型,
  2. keyof返回的是联合类型

[] 索引类型访问操作符,类似js访问对象的某个属性值的语法,在TS中,它可以用来访问某个属性的类型

class Person {
    name: string = "胡先生"
    age: number = 18
    time: Date = new Date()
    private UserId: number = 123
}

type PropNames = keyof Person // "name"|"age"|"time"

type NameType = Person["name"]

type PropTypes = Person[PropNames]
e540a3ce6322a90041cb342f6745c990.png

T["属性名"] 可以单独获取某个属性名的类型

5c2c93fd0ecab9decc21a5ced6285a83.png

T[keyof T] 可以拿到泛型T中所有 public属性的类型 的联合类型

有了上面的基础后,我们可以实现下 pick 函数,pick函数可以从对象上取出指定的属性

function pick(obj: T, prop: K): T[K] {return obj[prop]
}

K extends keyof T 指K可以赋值 给泛型T所有public属性 的联合类型,T[K]返回的就是泛型T上的 属性类型

const User = {
    name: "胡先生",
    age: 18,
    id: 12345
}
const nameValue = pick(User, "name")//胡先生
385ef51e8fe524618395fe07a08638a1.png

如图keyof T = "name"|"age"|"id"
所以K = "name"|"age"|"id" ,即prop的可选属性是"name"|"age"|"id"

2.映射类型

映射类型的语法是:[K in Keys] ,类似JS的数组方法forEach,遍历keys,并将值赋值给K
上面我们已经实现了一个pick函数,现在我们想实现一个TS工具类型MyPick,从泛型T中挑选出需要的属性

//定义一个泛型T,并且要从T中选出需要的属性,则要定义K 的类型是T所有public **属性名** 的联合类型
type MyPick={// K是一个联合类型,我们需要遍历K,使用映射类型的语法[K in Keys]
  [P in K]:T[P]// P是属性名,T[p]则可以拿到属性类型
}
type MyPick={
    [P in K]:T[P]
}interface User{
    name:string,
    age:number,
    id:number
}
type name = MyPick"name">
753a0485b9ed400d7951f5b4d68eef1a.png

TS工具类型中有个 Partial ,可以将所有类型变成可选的

// 定义一个泛型T
type MyPartial={// keyof T 可以拿到泛型T中所有pubilc的属性名// in 可以遍历所有属性名,并将属性名赋值给K// 则T[K]就是属性类型//?代表可选
  [K in keyof T]?:T[K]
}
type User = {
    id: number,
    name: string
}

type PartialUser = MyPartial
5e034d8daa0a0e38cd18ed47af2a59b4.png

3.条件类型

T extends U ? X : Y 如果T 能赋值给U,则返回类型X 否则返回类型Y

type IsStringOrNumber = T extends string ? string : number// 字符串"123"传递给了T,即"123"如果能赋值给string,则返回string,否正返回number
type str = IsStringOrNumber<"123">
81949a8643f0da7aa047ec24f703d035.png

注意 :是 T能不能赋值给U,而不是T是不是U类型,因为U可能是any,是所有类型的子类型,例如 type IsStringOrNumber = T extends any ? string : number 只要T是能赋值给any的任何类型,该表达式就只返回string类型

3.1条件类型和联合类型

条件类型和联合类型结合,可以形成 分布式有条件类型,举个例子

// 在T中找到不存在U中的类型,并返回
type Diff = T extends U ? never : T
type DiffType = Diffboolean, undefined | string>
389d8cf0b23592383a3fe41296039d40.png

上面的代码相当于

type DiffType2 = Diff 
  | Diff
                | Diff<boolean, undefined | string>
860f7cec8770406ff6359398f5adbe26.png

当T类型是联合类型number | string | boolean ,会分别将number | string | boolean

赋值给Diff
注意 :

  1. 只有裸类型参数才能实现分布式有条件类型,
  2. 裸类型参数,即类型参数不能被包裹在其他类型中,如数组,元组,函数,Promise等
// 泛型T被包裹在[T],所以不在是裸类型参数了
type Diff = [T] extends [U] ? never : T
type DiffType = Diffboolean, undefined | string>
41fea09c2f89095d9e8e850bc9e6e5e4.png

当T被数组包裹[T]时,就不能实现分布式有条件类型了,上面代码等同于

type DiffType = [number | string | boolean] extends [undefined | string] 
 ? never
     : number | string | boolean
b24465cd24f155b37a503be9c25ba846.png

3.2 条件类型与映射类型

现在我们实现一个类型工具,取出必选类型

interface User {
    id?: number
    name: string
    age: number
}


type NullableKeys = {// 如果k是id,T[k]=number|undefined// 所以只要undefined extends T[k] 就可以知道K是可选属性
    [K in keyof T]-?: undefined extends T[K] ? never : K
}[keyof T]
type keys = NullableKeys// [keyof T] = ["id"|"name"|"age"]
2d4287898db7f4df5b422e1216306a01.png

注意 :

  1. + - 用于映射类型中,给属性添加修饰符,-?即是减去可选的,将可续属性变成必选,-readonly将可读属性变成非只读
  2. 如果不写 -? ,虽然通过 undefined extends T[k] 找到了属性id,并且属性id的值被赋值为never,但是因为没有去除 ? ,TS会默认给 id 添加上 undefined 的值
type NullableKeys = {//不加-?
    [K in keyof T]: undefined extends T[K] ? never : K
}//注意这里没有keyof了
type keys = NullableKeys
f03a90cbb8c885626a7e6ad3cefe01db.png

如图,不添加-?,id被赋值为undefined

type NullableKeys = {//加-?
    [K in keyof T]-?: undefined extends T[K] ? never : K
}//注意这里没有keyof了
type keys = NullableKeys
b0f56bef96d48929b35da6565c100d91.png

4. infer

infer可以在条件语句中充当类待推断的类型变量
假如我们想知道一个数组中的元素类型,我们可能会这么做

type ElementOf = T extends Array ? string : T extends Array ? number : ...

按上面的写法,需要写出每个类型的条件语句,太过繁琐 我们可以看到数组的元素类型是一个变量,我们可以使用infer去声明这个变量

type ElementOf = T extends Array ? E :never
type strArr= Array
type arrType = ElementOf
bf3cb5c758509c618abc4f5ee56d6b38.png

5. 阅读utility-types的源码

github.com/piotrwitek/…

1.IfEquals

可以判断两个类型是否相同,并自定义返回的类型

type IfEquals = (() => T extends X ? 1 : 2)
 extends () => T extends Y ? 1 : 2
   ? A
   : B;

如果泛型X 和 泛型 Y相同,则返回 A,否正返回B

  1. (()=> T extends X ? 1 : 2)这是一个表达式,不会执行
  2. (()=> T extends Y ? 1 : 2) ,同上这是一个表达式
  3. 如果两条表达式相同,则返回A,否则返回B

2.MutableKeys

拿到一个对象的所有属性名( 除了readonly)的联合类型

/*
 * @example
 *   type Props = { readonly foo: string; bar: number };
 *
 *   // Expect: "bar"
 *   type Keys = MutableKeys;
 */
export type MutableKeys = {
  [P in keyof T]-?: IfEquals<
    { [Q in P]: T[P] },
    { -readonly [Q in P]: T[P] },// -readonly 是将只读属性变成非只读属性
    P
  >;
}[keyof T];
  1. 上面有疑问的地方是为啥需要[Q in P]:T[P],Q其实没有使用到,但如果没有写[Q in P],我们只能拿到P这个字符串,而不能拿到readonly这个属性
  2. 详情可以看这篇知乎www.zhihu.com/question/36…

3. ReadonlyKeys

拿到一个对象所有readonly的属性名的联合类型

/*
 * @example
 *   type Props = { readonly foo: string; bar: number };
 *
 *   // Expect: "foo"
 *   type Keys = ReadonlyKeys;
 */
export type ReadonlyKeys = {
  [P in keyof T]-?: IfEquals<
    { [Q in P]: T[P] },
    { -readonly [Q in P]: T[P] },
    never,
    P
  >;
}[keyof T];

4.NonUndefined

去除掉undefined

/** 
 * @example
 *   // Expect: "string | null"
 *   SymmetricDifference;
 */
export type NonUndefined = A extends undefined ? never : A;

5. FunctionKeys

拿到对象中的所有函数名(包括可选与必选)的联合类型

/** 
 * @example
 *  type MixedProps = {name: string; setName: (name: string) => void; someKeys?: string; someFn?: (...args: any) => any;};
 *
 *   // Expect: "setName | someFn"
 *   type Keys = FunctionKeys;
 */
export type FunctionKeys = {
  [K in keyof T]-?: NonUndefined extends Function ? K : never;
}[keyof T];
  1. T extends object 要求泛型T 必须能赋值给object,这样才能遍历T的属性名
  2. [K in keyof T] 遍历T的所有public属性名,并将属性名赋值给K
  3. -? T的每个字段都是必选,使得TS不会自动给字段赋值undefined
  4. 如果某个函数是一个可选属性,则 T[K] = ()=>{}|undefined ,那T[K] extends Function便不成立,所以需要用 NonUndefined 去除undefined的值

6.Pick

从泛型T中选出需要的属性

type Pick = {
    [P in K]: T[P];
};interface Person{
  name:string,
  id:number,
  age:number
}
type NewPerson = Pick"name"|"age">//{name:string,age:number}

7. RequiredKeys

拿到对象中所有必选的属性名的联合类型

/**
 * @example
 *   type Props = { req: number; reqUndef: number | undefined; opt?: string; optUndef?: number | undefined; };
 *
 *   // Expect: "req" | "reqUndef"
 *   type Keys = RequiredKeys;
 */
export type RequiredKeys = {
  [K in keyof T]-?: {} extends Pick ? never : K;
}[keyof T];
  1. {} extends {id:number} ? true : false 会返回 false ,因为id是必选属性,而 {} 对象中没有
  2. {} extends {id?:number} ? true : false 会返回 true ,因为id是可选属性,空对象也能赋值
  3. {} extends Pick ? never : K ,如果T[K]是可选属性则返回never,否则返回K
  4. -? 是让可选属性变成必选,不然 ,K?:neverK前面有个 ? TS会将其变成 K?:never|undefined ,这样子[keyof T]取出的值就含有undefined ,所以要加上 -?

8.PickByValue

从泛型T中的所有属性中找到属性类型能赋值给ValueType的属性

/**
 * @example
 *   type Props = { req: number; reqUndef: number | undefined; opt?: string; };
 *
 *   // Expect: { req: number }
 *   type Props = PickByValue;
 *   // Expect: { req: number; reqUndef: number | undefined; }
 *   type Props = PickByValue;
 */
export type PickByValue = Pick<
  T,
  { [Key in keyof T]-?: T[Key] extends ValueType ? Key : never }[keyof T]
>;
  1. Pick的第二个参数是一个联合类型,所以需要 [keyof T] 拿到泛型T的所有属性类型
  2. T[Key] extends ValueType ? Key : never 泛型T的属性类型能赋值给ValueType则返回Key,否则返回never
  3. 由1可知拿到属性类型,2可知[key in keyof T]:Key 可知{ [Key in keyof T]-?: T[Key] extends ValueType ? Key : never }[keyof T] 可以拿到T的所有属性名

9. SetDifference

A 如果能赋值给B,则返回never,否则返回A

/*
 * SetDifference (same as Exclude)
 * @desc Set difference of given union types `A` and `B`
 * @example
 *   // Expect: "1"
 *   SetDifference;
 *
 *   // Expect: string | number
 *   SetDifference void), Function>;
 */
export type SetDifference = A extends B ? never : A;

10.Omit

找到泛型T中除了K以外的其他属性

/*
 * @example
 *   type Props = { name: string; age: number; visible: boolean };
 *
 *   // Expect: { name: string; visible: boolean; }
 *   type Props = Omit;
 */
export type Omit = Pick>;
  1. keyof T 返回T的属性名的联合类型
  2. SetDifference 从T中找到不存在K中的属性名的联合类型C
  3. Pick 从T中找到属性名在C中的属性

11.Intersection

拿到两个泛型中共同的属性

/* @example
 *   type Props = { name: string; age: number; visible: boolean };
 *   type DefaultProps = { age: number };
 *
 *   // Expect: { age: number; }
 *   type DuplicateProps = Intersection;
 */
export type Intersection = Pick<
  T,
  Extract & Extract
>;
  1. type Extract = T extends U ? T : never;
  2. Extract & Extract 可以拿到T和U的属性的交集

12. Diff

找到T中不存在于U上的属性

/* @example
 *   type Props = { name: string; age: number; visible: boolean };
 *   type DefaultProps = { age: number };
 *
 *   // Expect: { name: string; visible: boolean; }
 *   type DiffProps = Diff;
 */
export type Diff = Pick<
  T,
  SetDifference
>;
  1. SetDifference 从T中找到不存在U中的属性的联合类型

13.DeepPartial

将所有属性递归变成可选属性

export type DeepPartial = T extends Function
  ? T
  : T extends Array
  ? _DeepPartialArray
  : T extends object
  ? _DeepPartialObject
  : T | undefined;/** @private */// tslint:disable-next-line:class-name
export interface _DeepPartialArray<T> extends Array<DeepPartial<T>> {}/** @private */
export type _DeepPartialObject = { [P in keyof T]?: DeepPartial };
  1. T extends Function 判断T是不是可以赋值给Function,是的话返回T,
  2. T extends Array 判断T是不是可以赋值给Array,因为不知道是什么类型的Array,所以用 infer U 作为一个类型变量代指,当T可以赋值给Array时,我们需要递归Array中的每一项,Array>
  3. T extends Object 判断T是不是可以赋值给Object,是的话,我们需要递归遍历Object中的每个属性, [P in keyof T] 遍历T的属性名, DeepPartial 将T 的属性类型传进 DeepPartial 进行递归

结语

如果有错漏的地方,还请看官们指正

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值