En el siguiente ejemplo, no puedo pensar en ninguna situación donde la asignación Pick<Object, Key> a Partial<Object> no sería sólida, por lo tanto, esperaría que esto se permitiera.

¿Alguien puede aclarar por qué no está permitido?

const fn = <T, K extends keyof T>(partial: Partial<T>, picked: Pick<T, K>) => {
    /*
    Type 'Pick<T, K>' is not assignable to type 'Partial<T>'.
        Type 'keyof T' is not assignable to type 'K'.
            'keyof T' is assignable to the constraint of type 'K', but 'K' could be instantiated with a different subtype of constraint 'string | number | symbol'.
    */
    partial = picked;
};

Ejemplo de patio de recreo TypeScript

2
Oliver Joseph Ash 27 jun. 2019 a las 13:16

1 respuesta

La mejor respuesta

@Titiancernicovadragomir es esencialmente correcto que el compilador generalmente no puede hacer un análisis de tipo sofisticado en tipos genéricos no resueltos. Lo hace mucho mejor con los tipos de hormigón. Consulte Microsoft / TyperScript # 28884 para una discusión sobre esto con Pick y Omit con conjuntos complementarios de llaves.

En estas situaciones, la única forma de proceder es que usted verifique personalmente que la asignación sea sólida y luego use una Tipo de aserción como en partial = picked as Partial<T> ...


... pero no haría eso en este caso. El error realmente es bueno aquí, aunque es difícil ver por qué desde que, esencialmente, simplemente sobrescribió la variable partial y no se hace nada con él dentro del alcance de la función. Por lo tanto, a pesar de ser erróneo, el código es inofensivo porque no se le ha permitido causar estragos en otros lugares. Vamos a desolarlo haciendo que fn() devuelva la variable partial modificada:

const fn = <T, K extends keyof T>(partial: Partial<T>, picked: Pick<T, K>) => {
  partial = picked; // error, for good reason
  return partial; // 😈
};

Por lo tanto, el problema básico es que Pick<T, K> es un tipo más ancho que T. Contiene las propiedades de T con las teclas en K, pero no se conoce no contener propiedades con las teclas no en {{x4} }. Quiero decir, un valor de tipo Pick<{a: string, b: number}, "a"> puede tener una propiedad b. Y si tiene uno, no tiene que ser de tipo number. Por lo que es un error asignar un valor de tipo Pick<T, K> a una variable de tipo Partial<T>.

Vamos a vaciar esto con un ejemplo tonto. Imagina que tiene una interfaz Tree y un objeto de tipo Tree, como este:

interface Tree {
  type: string;
  age: number;
  bark: string;
}

const tree: Tree = {
  type: "Aspen",
  age: 100,
  bark: "smooth"
};

Y también tiene una interfaz Dog y un objeto de tipo Dog, como este:

interface Dog {
  name: string;
  age: number;
  bark(): void;
}

const dog: Dog = {
  name: "Spot",
  age: 5,
  bark() {
    console.log("WOOF WOOF!");
  }
};

Entonces, dog y tree ambos tienen una propiedad numérica age, y ambos tienen una propiedad bark de diferentes tipos. Uno es un string y el otro es un método. Tenga en cuenta que dog es un valor perfectamente válido de tipo Pick<Tree, "age">, pero un valor de tipo Partial<Tree>. Y por lo tanto cuando llamas fn():

const partialTree = fn<Tree, "age">(tree, dog); // no error

Mi modificado fn() devuelve dog como Partial<Tree>, y comienza la diversión:

if (partialTree.bark) {
  partialTree.bark.toUpperCase(); // okay at compile time
  // at runtime "TypeError: partialTree.bark.toUpperCase is not a function"
}

Esa distensión se filtró precisamente porque no se sabe que Pick<T, K> excluye o restringe de otra manera las propiedades "implacables". Puede crear su propio StrictPicked<T, K> en el que las propiedades de T no en K están excluidas explícitamente:

type StrictPicked<T, K extends keyof T> = Pick<T, K> &
  Partial<Record<Exclude<keyof T, K>, never>>;

Y ahora su código es más sonido (ignorando cosas raras como K siendo un tipo de marca como en sobre el comentario anterior) ... pero el compilador todavía no puede verificarlo:

const fn2 = <T, K extends keyof T>(
  partial: Partial<T>,
  picked: StrictPicked<T, K>
) => {
  partial = picked; // also error
  partial = picked as Partial<T>; // have to do this
  return partial;
};

Eso sigue siendo el problema básico aquí; El compilador no puede lidiar fácilmente con cosas como esta. Tal vez algún día? Pero al menos no es tan fácil de usar en el lado de la persona que llama:

fn2<Tree, "age">(tree, dog); // error, dog is not a StrictPicked<Tree, "age">

De todos modos, espero que eso ayude. ¡Buena suerte!

Enlace al código

3
jcalz 27 jun. 2019 a las 19:20