TypeScript中如何正确类型化createAsyncThunk消除action的any类型
解决方法
你遇到的报错根源有两个:
- 你当前写的
createAsyncThunk没有显式声明返回值类型,且请求失败分支没有抛出错误,TS推断返回值可能为undefined,导致action.payload可能为空 - 原有Todo类型定义过于宽松,所有属性都是可选值,无法做精准类型校验
第一步:定义精准的类型
首先替换原有模糊的propsPayload定义,拆分出清晰的实体类型和请求参数类型:
// 定义Todo实体的精准类型 interface Todo { id: string; title: string; completed: boolean; } // 各个异步请求的参数类型 type GetTodosParams = void; type AddTodoParams = Pick<Todo, 'title'>; type ToggleCompleteParams = Pick<Todo, 'id' | 'completed'>; type DeleteTodoParams = Pick<Todo, 'id'>;
第二步:给createAsyncThunk补充泛型和错误抛出
createAsyncThunk的泛型规则为:createAsyncThunk<返回值类型, 入参类型>,同时在请求失败分支抛出错误,保证fulfilled状态下payload一定存在:
export const getTodosAsync = createAsyncThunk<{ todos: Todo[] }, GetTodosParams>( 'todos/getTodosAsync', async () => { const resp = await fetch('http://localhost:7000/todos'); if (resp.ok) { const todos = (await resp.json()) as Todo[]; return { todos }; } // 请求失败抛出错误,会走rejected分支,不会进入fulfilled回调 throw new Error('获取Todo列表失败'); } ); export const addTodoAsync = createAsyncThunk<{ todo: Todo }, AddTodoParams>( 'todos/addTodoAsync', async (payload) => { const resp = await fetch('http://localhost:7000/todos', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ title: payload.title }), }); if (resp.ok) { const todo = (await resp.json()) as Todo; return { todo }; } throw new Error('添加Todo失败'); } ); export const toggleCompleteAsync = createAsyncThunk<{ todo: Todo }, ToggleCompleteParams>( 'todos/completeTodoAsync', async (payload) => { const resp = await fetch(`http://localhost:7000/todos/${payload.id}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ completed: payload.completed }), }); if (resp.ok) { const todo = (await resp.json()) as Todo; return { todo }; } throw new Error('更新Todo状态失败'); } ); export const deleteTodoAsync = createAsyncThunk<{ id: string }, DeleteTodoParams>( 'todos/deleteTodoAsync', async (payload) => { const resp = await fetch(`http://localhost:7000/todos/${payload.id}`, { method: 'DELETE', }); if (resp.ok) { return { id: payload.id }; } throw new Error('删除Todo失败'); } );
第三步:调整初始化状态和reducers类型
替换原有模糊的初始化状态,同步调整普通reducers的类型校验:
// 初始化状态改为空数组,符合Todo类型 const initialState = [] as Todo[]; export const todoSlice = createSlice({ name: 'todos', initialState: initialState, reducers: { addTodo: (state, action: PayloadAction<AddTodoParams>) => { const todo: Todo = { id: nanoid(), title: action.payload.title, completed: false, }; state.push(todo); }, toggleComplete: (state, action: PayloadAction<ToggleCompleteParams>) => { const index = state.findIndex((todo) => todo.id === action.payload.id); if (index !== -1) { state[index].completed = action.payload.completed; } }, deleteTodo: (state, action: PayloadAction<DeleteTodoParams>) => { return state.filter((todo) => todo.id !== action.payload.id); }, }, // 这里直接去掉action的any即可,TS会自动推断正确的payload类型 extraReducers: (builder) => { builder .addCase(getTodosAsync.fulfilled, (state, action) => { return action.payload.todos; }) .addCase(addTodoAsync.fulfilled, (state, action) => { state.push(action.payload.todo); }) .addCase(toggleCompleteAsync.fulfilled, (state, action) => { const index = state.findIndex( (todo) => todo.id === action.payload.todo.id ); if (index !== -1) { state[index].completed = action.payload.todo.completed; } }) .addCase(deleteTodoAsync.fulfilled, (state, action) => { return state.filter((todo) => todo.id !== action.payload.id); }); } });
改完之后就可以完全去掉所有any类型,不会再出现payload可能为undefined的报错,同时所有类型校验都是精准的。
内容的提问来源于stack exchange,提问作者LeonardoFernandes
相关产品推荐
相关产品推荐

