TS类型编程-对象类型的指定属性可选的实践过程

1,277 阅读2分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第16天,点击查看活动详情

前言

现在有这么一个对象类型,描述了人类的部分信息:

interface Human {
  name: string
  age: number
  occupation: {
    name: string
    workingHours: number
  },
  say: () => void
}

现在因为需求的原因,需要根据这个 Human 对象类型,新建出一个具有相同属性的对象,而且可以指定部分属性可选,指定的可选属性根据需求会有变动。

代码

总所周知,TS 中有个 ? 属性修饰符,表示可选,因为新建的对象类型的属性必须和 Human 相同,所以可以考虑使用映射类型,将 Human 的属性映射到新的对象类型上面,同时将属性处理为可选:

// 映射类型
type ObjLimit1<T> = {
  [key in keyof T]?: T[key]
}

// 根据 Human 创建出一个具有相同的可选属性的对象
type NewHuman = ObjLimit1<Human>

微信截图_20220816173043.png

但是这种只会让全部属性都可选,可以再考虑传入一个泛型 K,用来指定可选属性的名称。

那这个 K 毫无疑问,必须的是 T 属性名称的联合类型,接着对从 K 中映射出的属性都加上可选操作符即可:

// 注意,必须定义 K 为 T 属性名称合集作为约束
// 因为后续需要使用 T[key] 取出对应属性的值的类型
type ObjLimit1<T, K extends keyof T> = {
  [key in K]?: T[key]
}

// 指定 name 和 age 两个属性可选,使用l
type NewHuman = ObjLimit1<Human, 'name' | 'age'>

微信截图_20220816192653.png

可以看到,只有 nameage 是可选的,但也只有这两个属性了,其他属性并没有加入新建的对象类型中去。

因为其他属性没有被包含在 K 里面,而且其他属性不是被指定为可选的,也不适合在这里处理。

其实接下来只要得到没有 nameage 的对象类型,把这个对象类型和上面实现对象类型进行交叉操作 & 即可。

那么目标很简单,就是获取没有 nameage 的对象类型,可以借助 Exclude 剔除指定的属性即可:

type ModifierLimit2<T, K extends keyof T> = {
  // 利用 Exclude 剔除指定的属性
  [P in Exclude<keyof T, K>]: T[P]
}

// 指定剔除 name 和 age
type NewHuman = ObjLimit2<Human, 'name' | 'age'>

微信截图_20220816193749.png

可以看到,已经获取了 nameage 之外的属性了,接下来就是进行交叉操作即可, 完整代码:

type ObjLimit1<T, K extends keyof T> = {
 [key in K]?: T[key]
}

type ObjLimit2<T, K extends keyof T> = {
 [key in Exclude<keyof T, K>]: T[key]
}

// 两种类型 交叉
type ObjLimit<T, K extends keyof T> = ObjLimit1<T, K> & ObjLimit2<T, K>

type NewHuman = ObjLimit<Human, 'name' | 'age'>

微信截图_20220816194516.png

可以看到,对象的属性还是那些属性,但是 nameage 已经被处理为可选。

至此,就是指定属性为可选的基本思路。