Búsqueda de sitios web

Typescript: Una interfaz con propiedades opcionales no se puede asignar a un tipo con las mismas propiedades pero obligatorias


Aquí está lo que quiero hacer:

const assign = <T extends U, U>(original: T, changes: U) =>
  <T>Object.assign({}, original, changes);

interface IFoo {
  bar: number,
  baz?: string,
  quux?: boolean,
}   

let foo = <IFoo>{ bar: 42, baz: 'hello' };

assign(foo, { baz: 'world' }); //Compilation Error:

Solo quiero implementar un assign seguro para tipos. pero el compilador lanza el error:

El argumento de tipo 'IFoo' no se puede asignar al parámetro de tipo '{ baz: string; }'. La propiedad 'baz' es opcional en el tipo 'IFoo', pero requerida en el tipo '{ baz: string; }'.

Las propiedades del tipo changes siempre deben ser un subconjunto estricto del tipo original, este tipo de asignación debería ser totalmente lógica, creo.

¿Cómo puedo sortearlo, apaciguando al compilador escrupuloso sin sacrificar la seguridad de tipos del parámetro changes?

Respuesta #1

Tus genéricos están equivocados, si te entiendo bien.

Debería ser:

const assign = <T>(original: T, changes: T) => <T> Object.assign({}, original, changes);

interface IFoo {
    bar?: number;
    baz?: string;
    quux?: boolean;
}   

let foo = <IFoo> { bar: 42, baz: 'hello' };

assign(foo, { baz: 'world' });

(código en el área de juegos)

Tanto tu original como tus cambios son del mismo tipo, solo que cada uno de ellos tiene diferentes propiedades definidas en la interfaz (basado en tu ejemplo, por supuesto).
No tienes dos tipos diferentes donde uno extienda al otro, así que <T extends U, U> es incorrecto.

Además, dado que quieres poder tener solo un subconjunto de las propiedades en la interfaz, debes hacerlas todas opcionales, de lo contrario obtendrás errores de compilación.

Editar

Según tu comentario, esto podría funcionar para ti:

const assign = <T extends U, U>(original: T, changes: U) => <T> Object.assign({}, original, changes);

interface FooOptional {
    baz?: string;
    quux?: boolean;
}

interface FooMandatory extends FooOptional {
    bar: number;
}

let foo = { bar: 42, baz: 'hello' };

assign(foo, { baz: 'world' });

(código en el área de juegos)

Respuesta #2

¿Realmente necesitas T extends U en assign, ya que estás "solo" interesado en la verificación de tipos de U?

const assign = <T, U>(original: T, changes: U) => <T> Object.assign({}, original, changes);

Podrías definirse como

interface U {
     baz: string
}


let foo = <IFoo>{ bar: 42, baz: 'hello' };
let u: U = { baz: 'world' }
assign(foo, u);

Artículos relacionados