Búsqueda de sitios web

Typescript accede al valor de un tipo genérico usando una clave restringida por el tipo de ese valor


Tengo una función de TypeScript que toma un tipo genérico y una clave para ese tipo genérico. Restringo esa clave para que la función solo acepte claves cuyos valores sean de un cierto tipo. Al acceder al objeto genérico usando la clave restringida, no obtengo el tipo esperado como resultado.

¿Cómo se restringe una clave de un objeto genérico a un tipo de valor específico y se accede a ese valor en una función genérica?

Por ejemplo:

function onlyTakesADateKey<T, K extends keyof T>(item: T, key: T[K] extends Date ? K : never): void {
    //I've constrained `key` to ensure that item[key] is a Date but this doesn't get typed as a Date
    const shouldBeADateButIsNot = item[key]
    //Property 'getTime' does not exist on type 'T[T[K] extends Date ? K : never]'.ts(2339)
    shouldBeADateButIsNot.getTime()
}
const foo = { key1: "asdf", key2: new Date() }
//Argument of type 'string' is not assignable to parameter of type 'never'.ts(2345)
const bar = onlyTakesADateKey(foo, "key1")
//It properly constrains the key, but per above can't access the date value in the function
const baz = onlyTakesADateKey(foo, "key2")

¿Por qué shouldBeADateButIsNot no es un Date? La clave está correctamente restringida. No puedo pasar argumentos a la función que hagan que no sea un Date.

Respuesta #1

El compilador realmente no puede hacer mucho con tipos condicionales que dependen de parámetros de tipo genérico aún no especificados como T y K dentro de la implementación de onlyTakesADateKey. Dentro de la implementación de la función, la evaluación del tipo T[K] extends Date ? K : never se aplaza. Por eso ves un error sobre T[T[K] extends Date ? K : never]. El compilador no es capaz de realizar el razonamiento de orden superior necesario para concluir que debe ser asignable a Date. Es una limitación de diseño de TypeScript, como se puede ver en microsoft/TypeScript#30728.

El compilador a menudo pospone la evaluación de tipos que dependen de genéricos no especificados, pero hay algunos lugares donde puede hacerlo un poco mejor. Uno es: si tienes un valor de tipo Record<K, V> y accedes a él con K, el compilador entenderá que es de tipo V. Así que las búsquedas genéricas no siempre se posponen por completo. Esto sugiere reescribir tus restricciones T y K de la siguiente manera:

function onlyTakesADateKey<T extends Record<K, Date>, K extends PropertyKey>(
  item: T, key: K): void {
    const isActuallyADateNow = item[key]
    isActuallyADateNow.getTime()
}

Esto funciona sin error, y tus ejemplos se comportan de manera similar:

const foo = { key1: "asdf", key2: new Date() }
const baz = onlyTakesADateKey(foo, "key2"); // okay

con la notable excepción de que cuando cometes un error, el compilador se queja de item y no de key:

const bar = onlyTakesADateKey(foo, "key1"); // error!
// -------------------------> ~~~
// Types of property 'key1' are incompatible.

Si realmente no quieres cambiar nada sobre tu llamada, siempre puedes usar una afirmación de tipo para simplemente decirle al compilador lo que no puede averiguar: que shouldBeADateButIsNot es un Date:

function onlyTakesADateKeyOrig<T, K extends keyof T>(
  item: T, key: T[K] extends Date ? K : never): void {
    const shouldBeADateButIsNot = item[key] as any as Date;
    shouldBeADateButIsNot.getTime()
}

Enlace de Playground al código

Artículos relacionados