The key value judgment in the loop of TS type gymnastics, the as keyword is used

The key value judgment in the loop of TS type gymnastics, the as keyword is used

Several topics will be discussed here, which are very representative, and these topics use a lot of keywords and TS syntax

3. Realize Omit 8・Readonly 2 2595・PickByType 2757・PartialByKeys

  • How to judge the key value in the loop of the object
  • What does P in keyof as any extends P mean
  • If the ampersand must be used, how to merge the contents of the & 2 sides before returning

The above selected questions are actually the same as the problem solving routines. After understanding the above three questions, these questions can be solved with templates.

00003-medium-omit topic implementation

Requirement: Omit will create a T object that omits the fields in K.

This is Pickvery similar to , but the result is opposite, Pick is to select the required fields, and Omit is to exclude the specified fields

  • According to the keywords we know, keyof must be used, but how to exclude other keys? neverUsed as a key, it can be excluded from the value of this field

So that leads to the first question, how to judge the key value in the loop of the object

Look at the answer first:

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

K extends keyof TThese grammars will not be explained, just like Pick, if you don't understand, you can read the first article, the prerequisite knowledge of TS gymnastics

The key is to P extends K ? never : Pjudge whether the key P belongs to the range of K. If it belongs, it needs to be excluded, and return to nerver. If it does not belong to it, it needs to return to the current P, which means to keep the current key.


Seeing this leads to the second question P in keyof as any extends P What does it mean

Add some parentheses to understand

[
  (P in keyof T) as
  (P extends K ? never : P)
]

Divide this code into 2 segments with as as the boundary

  • P in keyof TIt means traversal, this is easy to understand
  • as is here tentatively regarded as assertion
  • P extends K ? never : PHere is to determine the type

how do you connect

  • P loops over the range of T
  • P gets the keys in T
  • For this P we assert for him thatP / never
  • 如果 P 的这个键在 K 的范围中,我们就断言当前的 P 是 never(抛弃原先 P 的值),那么在对象循环的时候 never 就会被忽略掉,从而实现 Omit

00008-medium-readonly-2 题目详解

需求:这个就是 readonly 的 plus 版,指定字段来 readonly,如果没指定的则当作全部 readonly

好家伙,刚学完 Pick 和 Omit,又是指定字段,这不是手到擒来吗,用上 & 合并一下就完事了

下面是错误示范:

当时的错误思路是这样的:既然是指定字段 readonly,那我拿 原对象 和 指定字段循环出来的对象合并一下,循环的对象加个 readonly,让后面的字段覆盖前面的,那不就达成效果了吗

// 错误示范
type MyReadonly21<T, K extends keyof T = keyof T> = T & {
  readonly [P in K]: T[P]
}

结果肯定是报错的,因为 & 运算符计算出来的是 交集 ,简单点用段代码来说就是

type testReadonly = { title: string; name: string } & { readonly title: string; name: string }

// test 会报错:提示缺少了title字段
// Property 'title' is missing in type '{ name: string; }' but required in type '{ title: string; name: string; }'.
const test: testReadonly = {
  name: '111'
}

按道理 title 字段合并后应该也是只读,可是他变成了必填的了。 交集 细品,把 readonly 理解为 title 字段的一个额外的标签


正确答案如下:

用 Omit 挑选出必填的字段,然后在用 Pick 挑选出 readonly 的对象,这 2 个对象合并才是正确的答案

type MyReadonly2<T, K extends keyof T = keyof T> = {
  [P in keyof Omit<T, K>]: T[P]
} & {
  readonly [P in K]: T[P]
}

也有复杂的方案,就是假装我不会用 Omit ,用上面刚学的套路也可以解决这个问题

type MyReadonly21<T, K extends keyof T = keyof T> = {
  [P in keyof T as P extends K ? never : P]: T[P]
} & {
  readonly [P in K]: T[P]
}

02595-medium-pickbytype 题目实现

这个题目就和上面的套路一模一样!!也是需要在循环的时候就决定好哪些 key 要保留

不同的是要根据不同字段对应的类型来进行筛选,也就是说之前的 [P in keyof T as P extends U], 要换成 [P in keyof K as T[P] extends U ? nerver : P]

注意 T[P] extends U 的写法!就这么一个参数的变化,其余的该拿 P 还是拿 P,该 never 还是 never,这题就解决了

02757-medium-partialbykeys 题目详解

需求:指定字段来设置选填,如果没指定的则当作全部都选填

正是这道题,引出的问题 3: 如果一定要用到 & 符,如何在返回之前合并 & 2 边的内容

看到这个问题,是不是和 readonly2 的需求一样,无非就是把 readonly 换成 ?

代码啪一下就写完了,然后看测试用例 一个都没过

type PartialByKeys<T, K extends keyof T = keyof T> = {
  [P in keyof T as P extends K ? never : P]: T[P]
} & {
  [P in K]?: T[P]
}

可是仔细观察字段,该必填的有必填,该选填的有?。除了案例给出的是一个完整的对象,而我的是一个 {} & {} 交叉运算出来的,其他没啥区别呀

然后我仔细研究了一下测试用例,注意看 02757-partialbykeys 这题的测试用例,用的是 Equal 工具类

type cases = [
  Expect<Equal<PartialByKeys<User, 'name'>, UserPartialName>>,
  Expect<Equal<PartialByKeys<User, 'name' | 'unknown'>, UserPartialName>>,
  Expect<Equal<PartialByKeys<User, 'name' | 'age'>, UserPartialNameAndAge>>,
  Expect<Equal<PartialByKeys<User>, Partial<User>>>
]

而 08-readonl2 的测试用例,用的是 Alike

type cases = [Expect<Alike<MyReadonly21<Todo1>, Readonly<Todo1>>>]

好家伙。。如此说来, readonl2 算起来还不是完全的标准答案,顶多打个 98 分?

要解决这个问题也非常容易,我们只需要把 2 个合并一下就好了,至于怎么合并?

又有一个小妙招 —— 用 Pick,因为 Pick 能把字段都提出来,然后合并成一个新的对象,我们只需要把我们全部字段都放进去提取一次,就能合并在一起了

type Clone<T> = Pick<T, keyof T>

type PartialByKeys<T, K extends keyof T = keyof T> = Clone<
  {
    [P in keyof T as P extends K ? never : P]: T[P]
  } & {
    [P in K]?: T[P]
  }
>

到这里,常规的示例已经通过了,还留下一个 PartialByKeys<User, 'name' | 'unknown'> 在报错

因为他这里第二个字段传入的不一定在 User 里面,比如 unknown 就不在 User 的 key 中,而我们 K extends keyof T = keyof T 这个限制了参数的进来,所以只能把限制去掉,改成下面这样

type PartialByKeys2<T, K = keyof T> = Clone<
  {
    [P in keyof T as P extends K ? never : P]: T[P]
  } & {
    [P in K]?: T[P]
  }
>

去掉限制后,又新增了 2 个新的报错

  • K 的报错 Type 'K' is not assignable to type 'string | number | symbol'.
  • T[P] 的报错 Type 'P' cannot be used to index type 'T'.

因为确实没限制 k 的类型,而且 in 循环的则是 unio(联合类型)的变量
T[P] 因为 K 不一定就是 T 里面的键,T['unknown'] 肯定也是不存在的,所以报错了

所以当 K 循环的时候,还是用回刚才学的套路,在循环中判断键值 [P in K as P extends keyof T ? P : never]

T[P] 的问题解决就是先判断一下 P 是在 T 的范围内的才读 T[P] 就 OK 了

完整的正确答案如下:

type PartialByKeys2<T, K = keyof T> = Clone<
  {
    [P in keyof T as P extends K ? never : P]: T[P]
  } & {
    [P in K as P extends keyof T ? P : never]?: P extends keyof T ? T[P] : never
  }
>

最后

复盘一下几个问题

  • 如何在对象的循环中给键值做判断
    • as 关键字,as 后面接上条件语句,如果为 false,则返回 never 就可以排除掉某个键值了
  • P in keyof as any extends P ? never : P 是什么意思
    • P in keyof 是一组
    • extends P ? never : P 是一组
    • 最后 2 组的值用 as 链接起来,形成当前循环的键
  • 如果一定要用到 & 符,如何在返回之前合并 & 2 边的内容
    • & 符号是一个交叉合并的过程,合并出来的对象在约束数据的功能上没有区别
    • 可是交叉运算出来后的数据和单一类型用严格相等对比得到的结果是 false
    • Finally, based on the Pick tool, a Clone tool can be encapsulated and {} & {}combined into one{}

Guess you like

Origin juejin.im/post/7116117785830227981