Especificar el tipo de retorno en la función de flecha de TypeScript


118

Estoy usando React y Redux y tengo tipos de acción especificados como interfaces, de modo que mis reductores pueden aprovechar los tipos de unión etiquetados para mejorar la seguridad de los tipos.

Entonces, tengo declaraciones de tipo que se ven así:

interface AddTodoAction {
    type: "ADD_TODO",
    text: string
};

interface DeleteTodoAction {
    type: "DELETE_TODO",
    id: number
}

type TodoAction = AddTodoAction | DeleteTodoAction

Me gustaría crear funciones auxiliares que creen estas acciones, y tiendo a usar funciones de flecha para esto. Si escribo esto:

export const addTodo1 = (text: string) => ({
    type: "ADD_TODO",
    text
});

El compilador no puede proporcionar ninguna ayuda para asegurarse de que esto sea válido AddTodoActionporque el tipo de retorno no se especifica explícitamente. Puedo especificar el tipo de retorno explícitamente haciendo esto:

export const addTodo2: (text: string) => AddTodoAction = (text: string) => ({
    type: "ADD_TODO",
    text
})

Pero esto requiere especificar los argumentos de mi función dos veces, por lo que es detallado y más difícil de leer.

¿Hay alguna forma de que pueda especificar el tipo de retorno explícitamente cuando utilizo la notación de flechas?

He pensado en probar esto:

export const addTodo3 = (text: string) => <AddTodoAction>({
    type: "ADD_TODO",
    text
})

En este caso, el compilador ahora infiere el tipo de retorno como, AddTodoActionpero no valida que el objeto que estoy devolviendo tenga todos los campos apropiados.

Podría resolver esto cambiando a una sintaxis de función diferente:

export const addTodo4 = function(text: string): AddTodoAction {
    return {
        type: "ADD_TODO",
        text
    }
}

export function addTodo5(text: string): AddTodoAction {
    return {
        type: "ADD_TODO",
        text
    }
}

Cualquiera de estos métodos hará que el compilador use el tipo de retorno correcto y haga cumplir que he configurado todos los campos de manera apropiada, pero también son más detallados y cambian la forma en que ' this' se maneja en una función (lo que puede no ser un problema, Supongo.)

¿Algún consejo sobre la mejor forma de hacerlo?


6
getTitle = ():string => 'State Lists'
— Vikas Bansal

Respuestas:


147

Primero, considere la siguiente notación de su pregunta original:

export const addTodo3 = (text: string) => <AddTodoAction>({
    type: "ADD_TODO",
    text
})

Con esta notación, encasilla el objeto devuelto al tipo AddTodoAction. Sin embargo, el tipo de retorno declarado de la función aún no está definido (y el compilador asumirá implícitamente anycomo tipo de retorno).

En su lugar, utilice la siguiente notación:

export const addTodo3 = (text: string): AddTodoAction => ({
    type: "ADD_TODO",
    text: text
})

En este caso, omitir una propiedad requerida producirá el error esperado del compilador. Por ejemplo, omitir la textpropiedad generará el siguiente error (deseado):

Type '{ type: "ADD_TODO"; }' is not assignable to type 'TodoAction'.
  Type '{ type: "ADD_TODO"; }' is not assignable to type 'DeleteTodoAction'.
    Types of property 'type' are incompatible.
      Type '"ADD_TODO"' is not assignable to type '"DELETE_TODO"'.

Vea también el ejemplo del patio de recreo .


Esta es exactamente la notación que estaba buscando. Desafortunadamente, Visual Studio Code no resalta la sintaxis de esto de una manera que coincida con mis expectativas, pero el compilador parece entenderlo.
— Brian Hanechak

2

Creo que su mejor opción es crear una interfaz para su función que tenga los tipos correctos, luego solo necesita especificar ese tipo, no todos los tipos anidados de su interfaz:

interface AddTodoAction {
    type: "ADD_TODO",
    text: string
};

interface AddTodoActionCreator {
    (text: string): AddTodoAction;
};

export const addTodo: AddTodoActionCreator = (text) => ({
    type: "ADD_TODO",
    text
});

Actualización: cómo hacer esto con tipos

export interface GeneralAction<T> {
    type: string;
    payload: T;
}

export interface GeneralActionCreator<T> {
    (payload: T): GeneralAction<T>;
}

export const SAVE_EVENT = 'SAVE_EVENT';

export const SaveEvent: GeneralActionCreator<UserEvent> = (payload) => { return {type: SAVE_EVENT, payload}; };

2
¿Cómo se usarían los genéricos en este ejemplo? Digamos AddTodoActionCreator <T>
— Jannic Beck

1
¿Qué hay UserEventaquí?
— 1252748
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.