TypeScript类型体操挑战(十二)

114 阅读2分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第1天,点击查看活动详情

中等

PickByType

挑战要求

在线示例

type ExcludeKey<T, U, K extends keyof T = keyof T> = K extends K ? T[K] extends U ? K : never : never;

type PickByType<T, U> = {
  [P in ExcludeKey<T, U>]: T[P];
}
  • 我这里的是在使用in遍历T之前就将其中键进行过滤,主要发挥作用的是ExcludeKey

通过具体示例来看看ExcludeKey的作用:

// 假如有:
interface Model {
  name: string
  count: number
  isReadonly: boolean
  isEnable: boolean
}
type T = PickByType<Model, boolean>;

// 那么直接代入到 ExcludeKey 中则有:
// 此时 K = name | count | isReadonly | isEnable
type ExcludeKey<T, U, K extends keyof T = keyof T> = K extends K ? T[K] extends U ? K : never : never;

K extends K会产生分布,这样对每个键进行处理,那么最后获取的类型就是我们需要的键了。

我的答案略显复杂了点,在解答区看到一个更加简洁的:

type PickByType<T, U> = { [K in keyof T as T[K] extends U ? K : never]: T[K] }

TS4.1的版本以上,可以通过 as 来创建一个新的键类型,所以就能直接进行过滤了。

StartsWith

挑战要求

在线示例

type StartsWith<T extends string, U extends string> = T extends `${U}${string}` ? true : false;

EndsWith

挑战要求

在线示例

type EndsWith<T extends string, U extends string> = T extends `${string}${U}` ? true : false;
  • 这个跟StartsWith比,就是反过来了而已,所以还是一样的套路...

PartialByKeys

挑战要求

在线示例

type PartialByKeys<T extends {}, U = keyof T> = 
  Omit<Partial<Pick<T, U & keyof T>> & Omit<T, U & keyof T>, never>;

参考自解答区👍最多的

先通过一个具体的case来进行分析:

// case 如下:
interface User {
  name: string
  age: number
  address: string
}

interface UserPartialName {
  name?: string
  age: number
  address: string
}

Expect<Equal<PartialByKeys<User, 'name'>, UserPartialName>>
  1. 代入到类型中:
// 此时 T 为 User,U 为 'name'
type PartialByKeys = Omit<Partial<Pick<T, U & keyof T>> & Omit<T, U & keyof T>, never>;
  1. 先计算内部&右边的Omit
Omit<User, 'name' & keyof User>
// 相当于:(注意(),这是一个整体)
Omit<User, 'name' & ('name' | 'age' | 'address')>;
// & 是交叉类型运算符,返回的是交集,运算后结果为
Omit<User, 'name'>;
// 最终结果:
{
  age: number
  address: string
}

上面代码中,'name' & keyof User得到'name',因为它们的交集就是'name',如果没有交集,则会返回never,例如:

// T 的类型为 never
type T = 'username' & keyof User;
// 相当于:
type T = 'username' & keyof ('name' | 'age' | 'address');
  1. 计算左边 Omit 中的 Pick
Pick<User, 'name' & keyof User>
// 交叉类型运算符的结果跟第二步一样,返回 'name'
Pick<User, 'name'>
// 最终结果:
{
  name: string
}
  1. 根据上一步得到的结果,计算Partial的结果
Partial<{ name: string }>
// Partial 是内置工具函数,就是将类型 T 的全部属性变为可选的,则最终结果为:
{
  name?: string
}
  1. Omit内部&两侧的结果代入到类型中,则有:
// 因为 { name?: string } & { name: string } 是通过 & 运算符产生的类型
// 是两个类型的交集,所以需要将它们融合为一个类型
Omit<{ name?: string } & { name: string }>, never>;

// 使用 Omit 再进行一次运算,第二个参数可以传递 never
// 所以就达成将两个类型融为一体的目的了:
{
  name?: string
  age: number
  address: string
}