Búsqueda de sitios web

propiedades - cómo escribir correctamente las llaves de un objeto en TypeScript


Quiero recorrer las propiedades de un objeto y que TypeScript infiera correctamente el tipo de la clave, pero no parece que pueda lograrlo, y tengo que hacer un casting.

Aquí hay un ejemplo

type Cycle = 'year' | 'month' | 'week';

type Price = {
    [key in Cycle]?: number
}

const price1: Price = { year: 20, month: 40 }

for (const key in price1) {
    const cycle = key as keyof typeof price1 // wanna get rid of this cast
    console.log({ key, cycle }) // --> key: string, cycle: Cycle
}

Aquí está el playground de TypeScript si alguien quiere probarlo

Me gustaría saber por qué TypeScript no puede inferirlo, si hay algún "riesgo" al hacer esa conversión, y si hay una manera mejor, más elegante, segura o correcta de hacerlo.


Editar: teniendo en cuenta la respuesta que recibí, llegué a esta versión usando una función de protección de tipo con un predicado de tipo:

type Cycle = 'year' | 'month' | 'week';

type Price = {
    [key in Cycle]?: number
}

const price1: Price = { year: 20, month: 40 }

const isCycle = (cycle: any): cycle is Cycle => typeof(cycle) === 'string' && ['year', 'month', 'week'].includes(cycle)

for (const key in price1) {
    if (isCycle(key)) {
        console.log({ key }) // --> inside here key is a Cycle
    }
    console.log({ key })  // --> and here key is a string
}

Y este es el campo de pruebas de TypeScript

Sin embargo, tengo que mantener sincronizado el type Cycle='year' | 'month' | 'week' con la función isCycle, y se vuelve un poco verboso.

¿Hay una manera más simple de hacer esto de manera segura?

Respuesta #1

Esto no es posible de manera segura, y realmente no deberías hacer esa conversión (el término correcto es aserción de tipo).

La razón por la que el compilador nunca hará esta inferencia es porque en realidad no puede garantizar que key será solo uno de year, month o week.

Esto se debe a la tipificación estructural: en TypeScript, dado un tipo Price, es perfectamente válido asignar un objeto que tiene propiedades extras a una variable tipada como Price, así:

type Cycle = 'year' | 'month' | 'week';

type Price = {
    [key in Cycle]?: number
}

const otherPrice = {
    year: 20,
    month: 40,
    anotherWeirdKey: 'fish'
}

let price1: Price = { year: 20, month: 40 }
price1 = otherPrice

Esto no produce un error porque otherPrice tiene todas las propiedades correctas. Dado que los tipos no existen en tiempo de ejecución, eso significa que independientemente del tipo de price, tu bucle sí va a producir anotherWeirdKey como uno de sus valores.

Editar

Has editado tu pregunta para preguntar cómo crear un guardia de tipo que haga que tu código sea seguro sin tener que seguir actualizando el guardia de tipo.

La solución a esto es empezar con tus opciones en un literal de array y extraer un tipo de eso, en lugar de al revés. De esta manera, solo puedes reutilizar el literal de array en tu guardia de tipo. Así:

const cycle = [
    'year',
    'month',
    'week'
] as const

type Cycle = typeof cycle[number];

const isCycle = (cycle: string): cycle is Cycle => cycle.includes(cycle)

Artículos relacionados