Issue
I am using React and Redux and have action types specified as interfaces, so that my reducers can take advantage of tagged union types for improved type safety.
So, I have type declarations that look like this:
interface AddTodoAction {
type: "ADD_TODO",
text: string
};
interface DeleteTodoAction {
type: "DELETE_TODO",
id: number
}
type TodoAction = AddTodoAction | DeleteTodoAction
I'd like to make helper functions that create these actions, and I tend to use arrow functions for this. If I write this:
export const addTodo1 = (text: string) => ({
type: "ADD_TODO",
text
});
The compiler can't provide any help in making sure this is a valid AddTodoAction
because the return type isn't specified explicitly. I can specify the return type explicitly by doing this:
export const addTodo2: (text: string) => AddTodoAction = (text: string) => ({
type: "ADD_TODO",
text
})
But this requires specifying my function arguments twice, so it's verbose and harder to read.
Is there a way I can specify the return type explicitly when using arrow notation?
I've thought of trying this:
export const addTodo3 = (text: string) => <AddTodoAction>({
type: "ADD_TODO",
text
})
In this case, the compiler now infers the return type as AddTodoAction
but it's doesn't validate that the object I'm returning has all of the appropriate fields.
I could solve this by switching to a different function syntax:
export const addTodo4 = function(text: string): AddTodoAction {
return {
type: "ADD_TODO",
text
}
}
export function addTodo5(text: string): AddTodoAction {
return {
type: "ADD_TODO",
text
}
}
Either of these methods will cause the compiler to use the correct return type and enforce that I have set all fields appropriately, but they are also more verbose and they change the way 'this
' is handled in a function (which may not be an issue, I suppose.)
Is there any advice about the best way to do this?
Solution
First, consider the following notation from your original question:
export const addTodo3 = (text: string) => <AddTodoAction>({
type: "ADD_TODO",
text
})
Using this notation, you typecast the returned object to the type AddTodoAction
. However, the function's declared return type is still undefined (and the compiler will implicitly assume any
as return type).
Use the following notation instead:
export const addTodo3 = (text: string): AddTodoAction => ({
type: "ADD_TODO",
text: text
})
In this case, omitting a required property will yield the expected compiler error. For example, omitting the text
property will generate the following (desired) error:
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"'.
Also see the playground example.
Answered By - helmbert
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.