es.davy.ai

Preguntas y respuestas de programación confiables

¿Tienes una pregunta?

Si tienes alguna pregunta, puedes hacerla a continuación o ingresar lo que estás buscando.

La función “Switch” sobre los miembros de una unión discriminada en Typescript.

Supongamos que tengo una unión discriminada en Typescript:

type MyAction = { type: "redirect", url: string} | {type: "returnValue", value: number}

Quiero escribir una “expresión de switch” que tome un miembro de la unión discriminada y un mapeo de tipos a funciones procesadoras, y devuelve el resultado de ejecutar la función procesadora correcta:

let actionInstance = {type: "returnValue", value: 123} as MyAction
console.log(switchExpression(actionInstance, {
  redirect: (action) => action.url + "(url)",
  returnValue: (action) => `${action.value}` + "(value)"}
))

Quiero que las funciones procesadoras estén tipadas – es decir, quiero que Typescript dé un error si no se representan todos los tipos en el switch, si proporciono una clave para el switch que no es un tipo válido, o si asumo el tipo equivocado en una función procesadora (como intentar acceder a action.value en el valor para redirect).

Esto es lo que tengo hasta ahora, basado en algunas otras respuestas en StackOverflow:

type NarrowAction<t, n=""> = T extends { type: N } ? T : never;

type StuctureRecord<functionreturntype> =
    { [K in MyAction["type"]]: (value: NarrowAction<myaction, k="">) => FunctionReturnType }

function switchExpression<functionreturntype>(value: MyAction, cases: StuctureRecord<functionreturntype>): FunctionReturnType {
  let f = cases[value.type] as (value: any) => FunctionReturnType;
  return f(value);
}

Esto funciona correctamente, pero es específico para una unión discriminada (MyAction).
¿Hay alguna manera de adaptar este código para que funcione con cualquier unión discriminada donde todos los miembros tengan un campo type, en lugar de que solo funcione para MyAction? Intenté usar un tipo como DUType extends { type: string }, pero eso resulta en un montón de errores.

Nota: Sé que puedo usar directamente StructuredRecord o podría simplemente usar una sentencia switch regular o sentencias if/else con una función assertUnreachable (publicación de StackOverflow), pero estoy interesado en saber si existe una solución en la forma anterior debido a la curiosidad académica y al hecho de que sería una función de utilidad ergonómica para asegurar la seguridad del tipo sin tener que escribir todas las anotaciones de tipo directamente.</myaction,></t,>

Tags:  ,

Answer

  1. Avatar for davy.ai

    Para que el código funcione en cualquier unión discriminada con un campo type, podemos usar un parámetro de tipo genérico para representar la unión y su propiedad type. Aquí está el código actualizado:

    type NarrowUnion<t, k="" extends="" t['type']=""> = T extends { type: K } ? T : never;
    
    type StructuredRecord<t extends="" string,="" functionreturntype=""> = {
      [K in T]: (value: NarrowUnion<{ type: T }, K>) => FunctionReturnType;
    }
    
    function switchExpression<t extends="" string,="" functionreturntype="">(
      value: { type: T },
      cases: StructuredRecord<t, functionreturntype="">
    ): FunctionReturnType {
      const f = cases[value.type] as (value: any) => FunctionReturnType;
      return f(value);
    }
    

    Hemos agregado un parámetro de tipo genérico T para representar el tipo de la unión discriminada, y hemos actualizado el tipo NarrowAction para usar este parámetro para reducir la unión. También hemos agregado otro parámetro de tipo genérico FunctionReturnType para representar el tipo de retorno de las funciones de procesamiento.

    En lugar de usar MyAction['type'] para obtener los posibles valores del campo type, ahora podemos usar T directamente, ya que representa el nombre de la unión discriminada. También hemos actualizado el tipo StructuredRecord para tomar T como parámetro y usarlo para construir el tipo de registro.

    Finalmente, hemos actualizado la función switchExpression para tomar un valor con una propiedad type de tipo T, y hemos actualizado el parámetro cases para usar el tipo StructuredRecord con T como parámetro.

    Ahora podemos usar la función switchExpression con cualquier unión discriminada que tenga un campo type:

    type MyAction = { type: "redirect", url: string } | { type: "returnValue", value: number };
    type MyOtherAction = { type: "doSomething", data: string } | { type: "doSomethingElse", data: number };
    
    const actionInstance: MyAction = { type: "returnValue", value: 123 };
    console.log(switchExpression(actionInstance, {
      redirect: (action) => action.url + "(url)",
      returnValue: (action) => `${action.value}` + "(value)",
    }));
    
    const otherActionInstance: MyOtherAction = { type: "doSomething", data: "hello" };
    console.log(switchExpression(otherActionInstance, {
      doSomething: (action) => action.data.toUpperCase(),
      doSomethingElse: (action) => action.data.toString(),
    }));
    

    Este código funcionará correctamente y proporcionará seguridad de tipos tanto para MyAction como para MyOtherAction.</t,></t,>

Comments are closed.