Búsqueda de sitios web

typescript - ¿Por qué una variable tiene el tipo 'unknown' nuevamente después de la reasignación?


¿Por qué value es de tipo unknown de nuevo después de asignarle un string?

Ejemplo en TypeScript Playground

function example(): boolean {
  let value: unknown = 'something' // maybe here is some function call, which returns unknown

  if (!(typeof value === 'string') && !(typeof value === 'number')) {
    return false
  }

  value // string | number

  value = 'test'

  value // unknown

  return true
}

Respuesta #1

Acabas de encontrarte con el problema en microsoft/TypeScript#27706, que actualmente está marcado como una solicitud de función y no como un error. Pero definitivamente es confuso.


TypeScript realiza un estrechamiento basado en algunas reglas heurísticas. Algunas de estas reglas pueden estrechar valores del tipo unknown, mientras que otras solo funcionan correctamente con valores de tipos de unión. (Y unknown no es una unión).

Por ejemplo, las guardias de tipo typeof funcionan con unknown, como se menciona en microsoft/TypeScript#24439, la solicitud de extracción que introdujo unknown. Y funcionan con uniones. Así que estos se ven igual:

let value: string | number | boolean = 
  ["abc", 123, true][Math.floor(Math.random() * 3)];

if (!(typeof value === 'string') && !(typeof value === 'number')) {
  return false
}

value // string | number

y

let value: unknown = 
  ["abc", 123, true][Math.floor(Math.random() * 3)];

if (!(typeof value === 'string') && !(typeof value === 'number')) {
  return false
}

value // string | number

Pero la reducción por asignación no reduce unknown. Las asignaciones primero amplían la variable de nuevo a su tipo original (así que cualquier reducción anterior se restablecerá) y luego filtran la variable solo a aquellos miembros de la unión a los que se puede asignar el nuevo valor. Es como si estuvieras usando el tipo de utilidad Extract<T, U>.

Si tienes una variable de tipo string | number | boolean y le asignas un string, entonces la variable se reducirá a Extract<string | number | boolean, string>, que es string:

let value: string | number | boolean = "test";
value.toUpperCase(); // okay

Pero unknown no es una unión, si le asignas un string, el tipo resultante será Extract<unknown, string> que es simplemente unknown, y el compilador no entiende que es un string:

let value: unknown = 'something' 
value.toUpperCase(); // error!

Y esto es lo que provoca el comportamiento que estás viendo. Puedes usar typeof para reducir unknown a string | number, pero cuando haces una asignación vuelve a ampliarse a unknown. En microsoft/TypeScript#27706 se sugiere que las asignaciones a variables unknown deberían reducirse. Si estás de acuerdo, podrías darle un 👍 a ese problema, pero es un problema abierto desde hace bastante tiempo, así que no contaría con que algo cambie pronto. (Las mejoras recientes en la reducción no parecen afectar esto.)

Enlace de Playground al código