Website-Suche

TypeScript - Was ist der Unterschied zwischen den beiden Pick<T,K>-Util-Typ-Implementierungen


Ich probiere das aus: https://github.com/type-challenges/type-challenges/blob/master/questions/4-easy-pick/README.md

Ich kann den Unterschied zwischen ihnen nicht verstehen:

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]};

Du kannst es hier spielen: Linkbeschreibung hier eingeben

Testfall:

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;}

Ich bin über die Ergebnisse verwirrt, und warum?

Antwort #1

Erstes MyPick:

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

Kann zwei beliebige generische Typen akzeptieren. Ich meine, man kann es mit MyPick<1,2>/never oder MyPick<[], false>/never aufrufen, was natürlich keinen Sinn ergibt, aber trotzdem erlaubt ist, weil die Einschränkungen im Inneren der Utility überprüft werden.


Zweite MyPick:

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

Dieses MyPick hat Einschränkungen für beide Argumente. Das erste Argument sollte {} erweitern, was praktisch jeder any-Typ ist, und das zweite Argument sollte ein Schlüssel des ersten Arguments sein.

Zum Beispiel:

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

Wie Sie vielleicht bemerkt haben, dürfen Sie immer noch das erste Argument als primitiven Typ übergeben, da jeder Wert in JS ein Objekt ist und daher eigene Schlüssel hat.

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

Ich nehme an, Sie möchten eine Einschränkung anwenden, bei der der Benutzer nur erlaubt ist, ein Objekt als ersten Parameter zu übergeben, das bedeutet, dass Sie die Einschränkung Record<PropertyKey, any> verwenden sollten. Zum Beispiel:

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

Warum gibt MyPick1 einen Union-Typ zurück?

Sieh das:

// 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 gibt wegen der Distributivität eine Vereinigung zurück. Bitte lesen Sie diesen Abschnitt der Dokumentation gründlich. Dies ist eine sehr wichtige Funktion.

Wenn sich der K-Unionstyp verteilt, bedeutet das, dass diese Zeile { [P in K]: T[P] } auf jede Union angewendet wird.

Siehe ein weiteres Beispiel, um es klarer zu machen:

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")[]