Spécifier le type de retour dans la fonction de flèche TypeScript


118

J'utilise React et Redux et j'ai des types d'action spécifiés en tant qu'interfaces, afin que mes réducteurs puissent tirer parti des types d'union étiquetés pour une sécurité de type améliorée.

Donc, j'ai des déclarations de type qui ressemblent à ceci:

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

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

type TodoAction = AddTodoAction | DeleteTodoAction

J'aimerais créer des fonctions d'assistance qui créent ces actions, et j'ai tendance à utiliser des fonctions fléchées pour cela. Si j'écris ceci:

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

Le compilateur ne peut pas fournir d'aide pour s'assurer qu'il s'agit d'un valide AddTodoActioncar le type de retour n'est pas spécifié explicitement. Je peux spécifier le type de retour explicitement en faisant ceci:

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

Mais cela nécessite de spécifier deux fois mes arguments de fonction, donc c'est verbeux et plus difficile à lire.

Existe-t-il un moyen de spécifier explicitement le type de retour lors de l'utilisation de la notation fléchée?

J'ai pensé essayer ceci:

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

Dans ce cas, le compilateur déduit maintenant le type de retour comme AddTodoActionmais il ne valide pas que l'objet que je retourne a tous les champs appropriés.

Je pourrais résoudre cela en passant à une syntaxe de fonction différente:

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

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

L'une ou l'autre de ces méthodes amènera le compilateur à utiliser le bon type de retour et imposera que j'ai défini tous les champs de manière appropriée, mais elles sont également plus verbeuses et changent la façon dont ` this` est géré dans une fonction (ce qui peut ne pas être un problème, Je suppose.)

Y a-t-il des conseils sur la meilleure façon de procéder?


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

Réponses:


147

Tout d'abord, considérez la notation suivante de votre question initiale:

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

En utilisant cette notation, vous avez transtypé l'objet retourné dans le type AddTodoAction. Cependant, le type de retour déclaré de la fonction n'est toujours pas défini (et le compilateur supposera implicitement anycomme type de retour).

Utilisez plutôt la notation suivante:

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

Dans ce cas, l'omission d'une propriété obligatoire produira l'erreur attendue du compilateur. Par exemple, l'omission de la textpropriété générera l'erreur suivante (souhaitée):

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

Voir également l' exemple du terrain de jeu .


C'est exactement la notation que je recherchais. Malheureusement, la syntaxe de Visual Studio Code ne met pas cela en évidence d'une manière qui correspond à mes attentes, mais le compilateur semble le comprendre.
— Brian Hanechak

2

Je pense que votre meilleur pari est de créer une interface pour votre fonction qui a les bons types, il vous suffit de spécifier ce type, pas tous les types imbriqués de votre interface:

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

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

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

Mise à jour: comment faire cela avec les types

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
comment utiliserait-on des génériques dans cet exemple? Disons AddTodoActionCreator <T>
— Jannic Beck

1
Qu'y a-t-il UserEventici?
— 1252748
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.