Especifique o tipo de retorno na função de seta TypeScript


118

Estou usando o React e o Redux e tenho tipos de ação especificados como interfaces, para que meus redutores possam aproveitar as vantagens dos tipos de união marcados para melhorar a segurança de tipo.

Então, eu tenho declarações de tipo parecidas com estas:

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

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

type TodoAction = AddTodoAction | DeleteTodoAction

Gostaria de fazer funções auxiliares que criam essas ações e, para isso, costumo usar funções de seta. Se eu escrever isso:

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

O compilador não pode fornecer nenhuma ajuda para garantir que isso seja válido AddTodoActionporque o tipo de retorno não é especificado explicitamente. Posso especificar o tipo de retorno explicitamente fazendo o seguinte:

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

Mas isso requer a especificação de meus argumentos de função duas vezes, por isso é prolixo e mais difícil de ler.

Existe uma maneira de especificar o tipo de retorno explicitamente ao usar a notação de seta?

Eu pensei em tentar isso:

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

Nesse caso, o compilador agora infere o tipo de retorno como, AddTodoActionmas não valida se o objeto que estou retornando possui todos os campos apropriados.

Eu poderia resolver isso mudando para uma sintaxe de função diferente:

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

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

Qualquer um desses métodos fará com que o compilador use o tipo de retorno correto e reforce que eu configurei todos os campos apropriadamente, mas eles também são mais detalhados e mudam a maneira como ' this' é tratada em uma função (o que pode não ser um problema, Eu suponho.)

Existe algum conselho sobre a melhor maneira de fazer isso?


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

Respostas:


147

Primeiro, considere a seguinte notação de sua pergunta original:

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

Usando essa notação, você typecast o objeto retornado para o tipo AddTodoAction. No entanto, o tipo de retorno declarado da função ainda é indefinido (e o compilador assumirá implicitamente anycomo tipo de retorno).

Use a seguinte notação:

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

Nesse caso, omitir uma propriedade necessária resultará no erro esperado do compilador. Por exemplo, omitir a textpropriedade irá gerar o seguinte erro (desejado):

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"'.

Veja também o exemplo do playground .


Esta é exatamente a notação que eu estava procurando. Infelizmente, a sintaxe do Visual Studio Code não destaca isso de uma forma que corresponda às minhas expectativas, mas o compilador parece entender isso.
— Brian Hanechak

1

Acho que sua melhor aposta é criar uma interface para sua função que tenha os tipos corretos, então você só precisa especificar esse tipo, não todos os tipos aninhados de sua interface:

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

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

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

Atualização: como fazer isso com 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
como alguém usaria genéricos neste exemplo? Digamos AddTodoActionCreator <T>
— Jannic Beck

1
O que está UserEventaqui?
— 1252748 de
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.