typescript - ¿cuál es la diferencia entre las dos implementaciones del tipo utilitario Pick?
Estoy intentando esto: https://github.com/type-challenges/type-challenges/blob/master/questions/4-easy-pick/README.md
No puedo entender la diferencia entre ellos:
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]};
puedes jugarlo en: introduce la descripción del enlace aquí
caso de prueba:
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;}
Estoy confundido con los resultados, ¿y por qué?
Respuesta #1
Primero MyPick:
type MyPick<T, K> = T extends {} ? K extends keyof T ? {[P in K]: T[P]} : never : never
Puede aceptar cualquier dos tipos genéricos. Quiero decir, puedes llamarlo con MyPick<1,2>/nunca o MyPick<[], false>/nunca, lo cual, por supuesto, no tiene sentido pero todavía está permitido porque las restricciones se verifican dentro del cuerpo de la utilidad.
Segundo MyPick:
type MyPick<T extends {}, K extends keyof T> = {[P in K]: T[P]};
Este MyPick tiene restricciones para ambos argumentos. El primer argumento debe extender {}, que prácticamente es de tipo any, y el segundo argumento debe ser una clave del primer argumento.
Por ejemplo:
type Test0 = MyPick1<2, 'toString'> // ok
type Test1 = MyPick1<2, 3> // expected error
Como quizá hayas notado, todavía se te permite pasar el primer argumento como un tipo primitivo porque cada valor en JS es un objeto, por lo tanto tiene sus propias claves.
// "toString" | "toFixed" | "toExponential" | "toPrecision" | "valueOf" | "toLocaleString"
type NumberKeys = keyof number;
Asumo que quieres aplicar una restricción donde el usuario solo pueda pasar un objeto como primer argumento, lo que significa que deberías usar la restricción Record<PropertyKey, any>. Por ejemplo:
type Test0 = MyPick1<{ age: number }, 'age'> // ok
type Test1 = MyPick1<2, 'toString'> // error
type Test2 = MyPick1<true, 3> // expected error
¿Por qué MyPick1 devuelve un tipo Unión?
Mira esto:
// 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 devuelve una unión debido a la distributividad. Por favor, lea detenidamente esta sección de la documentación. Esta es una característica muy importante.
El tipo de unión K se distribuye, lo que significa que esta línea { [P in K]: T[P] } se aplica a cada unión.
Ve otro ejemplo, para que quede más claro:
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")[]
