Home > Enterprise >  what's the difference between the two Pick<T,K> util type implementations
what's the difference between the two Pick<T,K> util type implementations

Time:01-12

I'm trying this: https://github.com/type-challenges/type-challenges/blob/master/questions/4-easy-pick/README.md

I can't understand the difference between them:

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

type MyPick<T extends {}, K extends keyof T> = {[P in K]: T[P]};

you can play it at: enter link description here

test case:

type A = {name: string; value: number;}
type B = MyPick1<A, 'name' | 'value'> // got {name: string} | {value: number}
type C = MyPick2<A, 'name' | 'value'> // got {name: string; value: number;}

I'm confusing at the results, and why?

CodePudding user response:

First MyPick:

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

Can accept any two generic types. I mean, you can call it with MyPick<1,2> // never or MyPick<[], false> // never, which of course does not make sense but still allowed because constraints are checked inside the body of the utility.


Second MyPick:

type MyPick<T extends {}, K extends keyof T> = {[P in K]: T[P]};

This MyPick has a constraints for both arguments. FIrst argument should extend {} which is practically any type an second argument should be a key of first argument.

For example:

type Test0 = MyPick1<2, 'toString'> // ok
type Test1 = MyPick1<2, 3> // expected error

As you might have noticsd, you still allowed to pass first argument as a primitive type because every value in JS is an object, hence has its own keys.

// "toString" | "toFixed" | "toExponential" | "toPrecision" | "valueOf" | "toLocaleString"
type NumberKeys = keyof number;

I assume you want to apply a constraint where user only allowed to pass an object as a first argument, it means that you should use Record<PropertyKey, any> constraint. For Example:

type Test0 = MyPick1<{ age: number }, 'age'> // ok
type Test1 = MyPick1<2, 'toString'> // error
type Test2 = MyPick1<true, 3> // expected error

Why MyPick1 returns a Union type ?

See this:

// Slightly reduced
type MyPick1<T, K> = K extends keyof T ? { [P in K]: T[P] } : never

type A = { name: string; value: number; }

type B = MyPick1<A, 'name' | 'value'> // got {name: string} | {value: number}

MyPick1 returns a union because of distributivity. Please read thoroughly this section of the docs. This is very important feature.

K union type distributes, it means that this line { [P in K]: T[P] } is applied to each union.

See another one example, to make it more clear:

type Distibutive<T> = T extends any ? T[] : never

type Result1 = Distibutive<'a' | 'b'> // "a"[] | "b"[]

// This is how you disable distributivity
type NonDistibutive<T> = [T] extends [any] ? T[] : never

type Result2 = NonDistibutive<'a' | 'b'> // ("a" | "b")[]

  •  Tags:  
  • Related