typescript - ¿Cómo inferir argumentos basándose en el argumento que se proporcionó a la primera función?
Estoy tratando de crear una función utilitaria que tome un único valor de un enum y que infiera los argumentos de la función devuelta basándose en una interfaz con un campo type que coincida con el valor.
Estoy teniendo dificultades para mapear value y encontrar la interfaz con un campo type que coincida con el value
enum ActionTypes {
One = 'One',
Two = 'Two',
Three = 'Three'
}
interface Action1 {
type: ActionTypes.One
}
interface Action2 {
type: ActionTypes.Two
meta: {
fieldName: '2'
}
}
interface Action3 {
type: ActionTypes.Three
payload: number[]
meta: {
fieldName: '3'
}
}
type AllActions =
| Action1
| Action2
| Action3
const createAction = <T>(type: T) => (
payload?: //infer payload from AllActions.type === T,
meta?: //infer meta from AllActions.type === T,
) => ({
type,
...payload && { payload },
...meta && { meta }
})
const action1 = createAction(ActionTypes.One)
action1()
const action2 = createAction(ActionTypes.Two)
action2(undefined, {
meta: {
fieldName: '2'
}
})
const action3 = createAction(ActionTypes.Three)
action3([1, 2, 3], {
meta: {
fieldName: '3'
}
})
Campo de Juegos TS
Respuesta #1
Puedes escribir la función, pero hay varias cosas que necesitamos considerar:
Tdebería extenderAllActions['type']para capturar el tipo literal del valor pasado.Para extraer la acción asociada con
type: Tnecesitamos usar el tipo condicional predefinidoExtract<AllActions, { type: T }>Dado que según
Tla función interna puede tener un número variable de parámetros, necesitaremos usar una tupla en los parámetros rest para poder expandir un número variable de parámetros en la función resultante.Usar tuplas en el parámetro rest con tuplas declaradas manualmente resultará en la pérdida de nombres de parámetros, por lo que en lugar de construir una tupla como
[P, M]y expandirla, construiremos en su lugar una firma de función, extraeremos los parámetros como una tupla y expandiremos esa tupla nuevamente en un parámetro rest (function fn(...a: Parameters<(x: number) => void>) {}tendrá un único parámetro numérico llamadox, mientras quefunction fn(...a:[number]) {}tendrá un único parámetro numérico llamadoa_0, lo cual no es ideal)Podemos usar un tipo condicional personalizado para construir la firma deseada basada en la presencia de
payloadymeta.Dado que los tipos condicionales que contienen parámetros de tipo no resueltos son bastante problemáticos al escribir la implementación, usaremos una firma de implementación separada para la función interna.
Poniendo todas estas cosas juntas obtenemos:
type Payload<T> = T extends { payload: infer P } ? P : undefined;
type Metadata<T> = T extends { meta: infer M } ? { meta: M } : undefined;
type CreateActionParameteres<A> = /*4*/ Parameters<
/*5*/ A extends { payload: infer P, meta: infer M } ? /*4*/ (payload: P, meta: { meta: M }) => void :
/*5*/ A extends { payload: infer P } ? /*4*/ (payload: P) => void :
/*5*/ A extends { meta: infer M } ? /*4*/ (payload: undefined, meta: { meta: M }) => void :
() => void>
const createAction = <T extends AllActions['type'] /*1*/ >(type: T) =>
{
function inner(...a: CreateActionParameteres</*2*/ Extract<AllActions, { type: T }>>): /*2*/ Extract<AllActions, { type: T }>
function inner(payload?: Payload<AllActions>, meta?: Metadata<AllActions>) { /*6*/
return {
type,
...payload && { payload },
...meta && { meta }
};
}
return inner;
}
Enlace del parque
