Búsqueda de sitios web

typescript - ¿Cómo crear un objeto de un subtipo basado en un objeto de un supertipo?


Tengo un objeto compactUser de un supertipo y quiero crear un objeto detailedUser de un subtipo basado en el objeto compactUser, de manera segura en cuanto al tipo.

Tengo una solución que funciona, detailedUser1 en el código a continuación, pero me gustaría algo más flexible (en la línea de detailedUser2) para escenarios más complicados.

(Lo siento, esto parece una pregunta básica de TypeScript, busqué una solución, pero terminé preguntando aquí.)

(código)

interface CompactUser {
    id: number
}

interface DetailedUser {
    id: number
    name: string
    sex: string
}

const compactUser: CompactUser = {id: 1}

const detailedUser1: DetailedUser = {
    ...compactUser,
    name: 'nume',
    sex: 'm',
}

// I don't like this; it's more flexible, but error prone
const detailedUser2 = compactUser as DetailedUser

// ups, I set `sex`, but forgot about `name`
detailedUser2.sex = Math.random() > 0.5 ? 'f' : 'm'

// not ok - I access `name` and there's no typescript error
console.log(detailedUser2.name)

Respuesta #1

Tengo una solución que funciona, detailedUser1 en el código de abajo

La forma en que lo estás haciendo es la forma en que lo haces, con notación de propagación como en tu ejemplo:

const detailedUser1: DetailedUser = {
    ...compactUser,
    name: 'nume',
    sex: 'm',
};

...o Object.assign:

const detailedUser1: DetailedUser = Object.assign(
    {},
    compactUser,
    {
        name: 'nume',
        sex: 'm',
    }
};

De esos, diría que spread es el más claro y común. Ten en cuenta que ambos conservan todas las propiedades de compactUser, pero en tu escenario compactUser solo tiene propiedades apropiadas para DetailedUser, así que está bien.

Si hay escenarios más complicados, probablemente quieras ponerlos en una función reutilizable, pero serán fundamentalmente similares.

Me gustaría algo más flexible (en la línea de detailedUser2)...

Tu detailsUser2 no hace nada en absoluto para que el objeto se ajuste al tipo, solo le asegura a TypeScript que es del tipo requerido. A partir de ese momento, el objeto está desincronizado con su información de tipo, porque no tiene name ni sex, pero su tipo dice que sí:

console.log("sex" in detailedUser2); // false, but the type says it has to be true

Copia en vivo

Por eso tienes que hacer algo como lo que has hecho para detailedUser1.

Si quieres reutilizar el mismo objeto con información de tipo actualizada, podrías hacer esto:

const detailedUser2: DetailedUser = Object.assign(compactUser, {name: "x", sex: "f"});

Por ejemplo, proporciona las propiedades faltantes. Eso actualiza compactUser en el lugar. (Y de nuevo, si compactUser tiene propiedades que DetailedUser no tiene, seguirán allí aunque el tipo diga que no; pero nuevamente, ese no es el caso en tu ejemplo.)

Una de las ventajas tanto de tu forma detailedUser1 como de la Object.assign anterior es que si olvidas una propiedad, TypeScript te lo dirá:

const detailedUser2: DetailedUser = {...compactUser, {name: "x"});
//    ^^^^^^^^^^^^^-- Property 'sex' is missing in type 'CompactUser & { name: string; }' but required in type 'DetailedUser'.

y

const detailedUser2: DetailedUser = Object.assign(compactUser, {name: "x"});
//    ^^^^^^^^^^^^^-- Property 'sex' is missing in type 'CompactUser & { name: string; }' but required in type 'DetailedUser'.

Copia en vivo