¿Por qué TypeScript acepta la definición de seta cuando no devuelve objetos de tipo A ?
type A = { a: '123', b: '456' } // Returns copy of obj with obj[k] = '933' function seta<K extends keyof A>(k: K, obj: A): A { return { ...obj, [k]: '933' } } const w: A = seta('a', { a: '123', b: '456' }) // now w = {a: '933', b: '456'}Se acepta porque si pasas el cursor sobre [k]: '933', dice
(parámetro) k: K extiende la tecla de A
Lo que significa que está devolviendo una propiedad que se extiende desde el tipo de devolución, que está permitido.
Cuando declara que un objeto debe ser de una interfaz, está diciendo que debe tener al menos esas propiedades, no solo esas propiedades.
Esto parece un error o una limitación en TypeScript; consulte microsoft/TypeScript#37103 (etiquetado como error) o microsoft/TypeScript#32236 (etiquetado como "necesita investigación").
Cuando distribuye propiedades en un objeto literal y luego agrega una propiedad calculada , parece que TypeScript ignorará por completo la propiedad calculada a menos que la clave calculada sea de un tipo literal único y específico (no una unión de literales, y no un tipo genérico parámetro restringido a un literal de cadena):
function testing<K extends "a" | "b">(a: "a", x: string, y: "a" | "b", z: K) { const someObject = { a: "v" } // { a: string } const objA = { ...someObject, [a]: 0 } // { a: number } 👍 const objX = { ...someObject, [x]: 0 } // { a: string } 👎 const objY = { ...someObject, [y]: 0 } // { a: string } 👎 const objZ = { ...someObject, [z]: 0 } // { a: string } 👎 } Las claves de propiedad calculadas en general son un poco problemáticas en TypeScript, incluso sin propagarse, ya que tienden a ensancharse hasta llegar a string , perdiendo información que podría interesarle (vea otro error en microsoft/TypeScript#13948 ):
function testing2<K extends "a" | "b">(a: "a", x: string, y: "a" | "b", z: K) { const objA = { [a]: 0 } // { a: number } 👍 const objX = { [x]: 0 } // { [x: string]: number; } 🤷♂️ const objY = { [y]: 0 } // { [x: string]: number; } 🤷♂️ const objZ = { [z]: 0 } // { [x: string]: number; } 🤷♂️ }Así que ese es el problema que tienes aquí.
No sé qué decir aparte de "pisar con cuidado las propiedades calculadas". Puede solucionarlo definiendo su propia función que produce una versión más "correcta" de lo que sería una propiedad calculada:
function kv<K extends PropertyKey, V>(k: K, v: V) { return { [k]: v } as { [P in K]: { [Q in P]: V } }[K] } const k = Math.random() < 0.5 ? "a" : "b"; const obj = kv(k, Math.random()); /* const obj: { a: number; } | { b: number; } */Puedes ver que una unión de claves produce una unión de objetos. Pero si la clave es genérica, el compilador deja eso sin evaluar y la distribución con genéricos tiende a representarse como una intersección , lo que a menudo es aceptable, pero no cuando las claves se superponen. (Consulte microsoft/TypeScript#32022 para ese problema):
function setA2<K extends keyof A>(k: K, obj: A): A { return { ...obj, ...kv(k, '933') } /* const ret1: A & { [P in K]: { [Q in P]: string; }; }[K] */ // no error } Entonces, tendría que ampliar k de K a keyof A antes de recibir una advertencia:
function setA3<K extends keyof A>(k: K, obj: A): A { const kW: keyof A = k; return { ...obj, ...kv(kW, '933') }; // FINALLY AN ERROR // Type '{ a: string; b: "456"; } | { b: string; a: "123"; }' is not assignable to type 'A'. }Entonces, hurra, supongo... puedes esforzarte mucho para sacar algo de seguridad de esta función, pero es una victoria bastante pírrica ( creo que es una palabra). De nuevo, tenga cuidado con las propiedades calculadas hasta que se solucionen los problemas relevantes de GitHub.