将 AsyncThunkAction 传递给 unwrapResult
Passing an AsyncThunkAction to unwrapResult
我正在学习 Redux Essentials 教程,并且 运行 在第 5 部分 Async Logic and Data Fetching 中遇到了问题。我正在使用 TypeScript,尽管教程中没有使用 TypeScript,因为我正在尝试同时学习 Redux 和 TypeScript。
在 Checking Thunk Results in Components 部分中,我在调用 Redux 的 unwrapResult
函数时遇到类型错误,我无法弄清楚。
这是错误:
TypeScript error in redux-essentials-example-app/src/features/posts/AddPostForm.tsx(34,22):
Argument of type 'AsyncThunkAction<Post, InitialPost, {}>' is not assignable to parameter of type 'ActionTypesWithOptionalErrorAction'.
Property 'payload' is missing in type 'AsyncThunkAction<Post, InitialPost, {}>' but required in type '{ error?: undefined; payload: any; }'. TS2345
32 | setAddRequestStatus("pending");
33 | const result = await dispatch(addNewPost({ title, content, user: userId }));
> 34 | unwrapResult(result);
| ^
35 | setTitle("");
36 | setContent("");
37 | setUserId("");
这是我打字的 AddPostForm.tsx 版本的全部内容:
import React, { useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { unwrapResult } from "@reduxjs/toolkit";
import { RootState } from "../../app/store";
import { addNewPost } from "./postsSlice";
export default function AddPostForm() {
const [title, setTitle] = useState("");
const [content, setContent] = useState("");
const [userId, setUserId] = useState("");
const [addRequestStatus, setAddRequestStatus] = useState("idle");
const dispatch = useDispatch();
const users = useSelector((state: RootState) => state.users);
const onTitleChanged = (e: React.ChangeEvent<HTMLInputElement>) => {
setTitle(e.target.value);
};
const onContentChanged = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
setContent(e.target.value);
};
const onAuthorChanged = (e: React.ChangeEvent<HTMLSelectElement>) => {
setUserId(e.target.value);
};
const canSave = [title, content, userId].every(Boolean) && addRequestStatus === "idle";
const onSavePostClicked = async () => {
if (canSave) {
try {
setAddRequestStatus("pending");
const result = await dispatch(addNewPost({ title, content, user: userId }));
unwrapResult(result);
setTitle("");
setContent("");
setUserId("");
} catch (err) {
console.error("Failed to save the post: ", err);
} finally {
setAddRequestStatus("idle");
}
}
};
const usersOptions = users.map(user => (
<option key={user.id} value={user.id}>
{user.name}
</option>
));
return (
<section>
<h2>Add a new post</h2>
<form>
<label htmlFor="postTitle">Post Title:</label>
<input type="text" id="postTitle" name="postTitle" value={title} onChange={onTitleChanged} />
<label htmlFor="postAuthor">Author:</label>
<select id="postAuthor" value={userId} onChange={onAuthorChanged}>
<option value=""></option>
{usersOptions}
</select>
<label htmlFor="postContent">Content:</label>
<textarea id="postContent" name="postContent" value={content} onChange={onContentChanged} />
<button type="button" onClick={onSavePostClicked} disabled={!canSave}>Save post</button>
</form>
</section>
);
}
这是我输入的 redux-essentials-example-app/src/features/posts/postsSlice.tsx 版本的全部内容:
import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";
import { RootState } from "../../app/store";
import { client } from "../../api/client";
export interface PostState {
posts: Post[],
status: "idle" | "loading" | "succeeded" | "failed",
error: string | null,
}
export interface Post {
id: string,
date: string,
title: string,
content: string,
user: string,
reactions: Reactions,
}
export interface Reactions {
thumbsUp: number,
hooray: number,
heart: number,
rocket: number,
eyes: number,
[key: string]: number,
}
const initialState: PostState = {
posts: [],
status: "idle",
error: null,
};
export const fetchPosts = createAsyncThunk("posts/fetchPosts", async () => {
const response = await client.get("/fakeApi/posts");
return response.posts;
});
interface InitialPost {
title: string,
content: string,
user: string,
}
export const addNewPost = createAsyncThunk<Post, InitialPost>(
"posts/addNewPost",
async (initialPost) => {
const response = await client.post("/fakeApi/posts", { post: initialPost });
return response.post;
}
);
const postsSlice = createSlice({
name: "posts",
initialState,
reducers: {
postUpdated: (state, action) => {
const { id, title, content } = action.payload;
const existingPost = state.posts.find(post => post.id === id);
if (existingPost) {
existingPost.title = title;
existingPost.content = content;
}
},
reactionAdded: (state, action) => {
const { postId, reaction } = action.payload;
const existingPost = state.posts.find((post: Post) => post.id === postId);
if (existingPost) {
existingPost.reactions[reaction]++;
}
},
},
extraReducers: builder => {
builder.addCase(fetchPosts.pending, (state) => {
state.status = "loading";
});
builder.addCase(fetchPosts.fulfilled, (state, action) => {
state.status = "succeeded";
state.posts = state.posts.concat(action.payload);
});
builder.addCase(fetchPosts.rejected, (state, action) => {
state.status = "failed";
if (action.error.message) {
state.error = action.error.message;
}
});
builder.addCase(addNewPost.fulfilled, (state, action) => {
state.posts.push(action.payload);
});
},
});
export const { postUpdated, reactionAdded } = postsSlice.actions;
export function selectAllPosts(state: RootState) {
return state.posts.posts;
}
export function selectPostById(state: RootState, postId: string) {
return state.posts.posts.find((post: Post) => post.id === postId);
}
export default postsSlice.reducer;
我看了一下Redux Toolkit的源码,ActionTypesWithOptionalErrorAction
类型没有导出,看来传递给unwrapResult
的对象需要是某种形状而不是声明为某种类型。类型错误表明缺少 payload
属性,但如果我注释掉 unwrapResult
调用并使用 console.log(result)
检查对象,它肯定存在。因此,这似乎是使类型正确而不是代码中的逻辑错误的问题。如何正确输入?
我明白了。从 react-redux 调用 useDispatch
时需要指定“调度”类型。 Usage With TypeScript: Getting the Dispatch
Type.
中对此进行了描述
最后,我将这些额外的类型添加到创建我的 Redux 存储的文件中:
export type AppDispatch = typeof store.dispatch;
export function useAppDispatch() {
return useDispatch<AppDispatch>();
}
然后在 src/features/posts/AddPostForm.tsx 中,我导入了 useAppDispatch
而不是 useDispatch
并使用了它。
我刚刚在尝试解决同样的问题时遇到了这个问题。我没有使用这些类型创建一个 useAppDispatch()
函数到 fiddle,而是意识到我来自 configureMiddleware 的存储不包括 thunk 类型作为调度操作。
在 this page,他们说使用中间件定义的回调将包括正确的类型。我曾经使用过的地方:
const store = configureStore({
reducer: rootReducer,
middleware: […getDefaultMiddleware(), logger]
})
将其更改为
const store = configureStore({
reducer: rootReducer,
middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(logger)
})
实际上解决了问题,因为 store.dispatch()
现在将 AsyncThunkAction
包含在可接受的参数类型列表中。这是一个更好的解决方案,但我希望它包含在关于 configureStore
.
的 redux 工具包文档中
我正在学习 Redux Essentials 教程,并且 运行 在第 5 部分 Async Logic and Data Fetching 中遇到了问题。我正在使用 TypeScript,尽管教程中没有使用 TypeScript,因为我正在尝试同时学习 Redux 和 TypeScript。
在 Checking Thunk Results in Components 部分中,我在调用 Redux 的 unwrapResult
函数时遇到类型错误,我无法弄清楚。
这是错误:
TypeScript error in redux-essentials-example-app/src/features/posts/AddPostForm.tsx(34,22):
Argument of type 'AsyncThunkAction<Post, InitialPost, {}>' is not assignable to parameter of type 'ActionTypesWithOptionalErrorAction'.
Property 'payload' is missing in type 'AsyncThunkAction<Post, InitialPost, {}>' but required in type '{ error?: undefined; payload: any; }'. TS2345
32 | setAddRequestStatus("pending");
33 | const result = await dispatch(addNewPost({ title, content, user: userId }));
> 34 | unwrapResult(result);
| ^
35 | setTitle("");
36 | setContent("");
37 | setUserId("");
这是我打字的 AddPostForm.tsx 版本的全部内容:
import React, { useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { unwrapResult } from "@reduxjs/toolkit";
import { RootState } from "../../app/store";
import { addNewPost } from "./postsSlice";
export default function AddPostForm() {
const [title, setTitle] = useState("");
const [content, setContent] = useState("");
const [userId, setUserId] = useState("");
const [addRequestStatus, setAddRequestStatus] = useState("idle");
const dispatch = useDispatch();
const users = useSelector((state: RootState) => state.users);
const onTitleChanged = (e: React.ChangeEvent<HTMLInputElement>) => {
setTitle(e.target.value);
};
const onContentChanged = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
setContent(e.target.value);
};
const onAuthorChanged = (e: React.ChangeEvent<HTMLSelectElement>) => {
setUserId(e.target.value);
};
const canSave = [title, content, userId].every(Boolean) && addRequestStatus === "idle";
const onSavePostClicked = async () => {
if (canSave) {
try {
setAddRequestStatus("pending");
const result = await dispatch(addNewPost({ title, content, user: userId }));
unwrapResult(result);
setTitle("");
setContent("");
setUserId("");
} catch (err) {
console.error("Failed to save the post: ", err);
} finally {
setAddRequestStatus("idle");
}
}
};
const usersOptions = users.map(user => (
<option key={user.id} value={user.id}>
{user.name}
</option>
));
return (
<section>
<h2>Add a new post</h2>
<form>
<label htmlFor="postTitle">Post Title:</label>
<input type="text" id="postTitle" name="postTitle" value={title} onChange={onTitleChanged} />
<label htmlFor="postAuthor">Author:</label>
<select id="postAuthor" value={userId} onChange={onAuthorChanged}>
<option value=""></option>
{usersOptions}
</select>
<label htmlFor="postContent">Content:</label>
<textarea id="postContent" name="postContent" value={content} onChange={onContentChanged} />
<button type="button" onClick={onSavePostClicked} disabled={!canSave}>Save post</button>
</form>
</section>
);
}
这是我输入的 redux-essentials-example-app/src/features/posts/postsSlice.tsx 版本的全部内容:
import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";
import { RootState } from "../../app/store";
import { client } from "../../api/client";
export interface PostState {
posts: Post[],
status: "idle" | "loading" | "succeeded" | "failed",
error: string | null,
}
export interface Post {
id: string,
date: string,
title: string,
content: string,
user: string,
reactions: Reactions,
}
export interface Reactions {
thumbsUp: number,
hooray: number,
heart: number,
rocket: number,
eyes: number,
[key: string]: number,
}
const initialState: PostState = {
posts: [],
status: "idle",
error: null,
};
export const fetchPosts = createAsyncThunk("posts/fetchPosts", async () => {
const response = await client.get("/fakeApi/posts");
return response.posts;
});
interface InitialPost {
title: string,
content: string,
user: string,
}
export const addNewPost = createAsyncThunk<Post, InitialPost>(
"posts/addNewPost",
async (initialPost) => {
const response = await client.post("/fakeApi/posts", { post: initialPost });
return response.post;
}
);
const postsSlice = createSlice({
name: "posts",
initialState,
reducers: {
postUpdated: (state, action) => {
const { id, title, content } = action.payload;
const existingPost = state.posts.find(post => post.id === id);
if (existingPost) {
existingPost.title = title;
existingPost.content = content;
}
},
reactionAdded: (state, action) => {
const { postId, reaction } = action.payload;
const existingPost = state.posts.find((post: Post) => post.id === postId);
if (existingPost) {
existingPost.reactions[reaction]++;
}
},
},
extraReducers: builder => {
builder.addCase(fetchPosts.pending, (state) => {
state.status = "loading";
});
builder.addCase(fetchPosts.fulfilled, (state, action) => {
state.status = "succeeded";
state.posts = state.posts.concat(action.payload);
});
builder.addCase(fetchPosts.rejected, (state, action) => {
state.status = "failed";
if (action.error.message) {
state.error = action.error.message;
}
});
builder.addCase(addNewPost.fulfilled, (state, action) => {
state.posts.push(action.payload);
});
},
});
export const { postUpdated, reactionAdded } = postsSlice.actions;
export function selectAllPosts(state: RootState) {
return state.posts.posts;
}
export function selectPostById(state: RootState, postId: string) {
return state.posts.posts.find((post: Post) => post.id === postId);
}
export default postsSlice.reducer;
我看了一下Redux Toolkit的源码,ActionTypesWithOptionalErrorAction
类型没有导出,看来传递给unwrapResult
的对象需要是某种形状而不是声明为某种类型。类型错误表明缺少 payload
属性,但如果我注释掉 unwrapResult
调用并使用 console.log(result)
检查对象,它肯定存在。因此,这似乎是使类型正确而不是代码中的逻辑错误的问题。如何正确输入?
我明白了。从 react-redux 调用 useDispatch
时需要指定“调度”类型。 Usage With TypeScript: Getting the Dispatch
Type.
最后,我将这些额外的类型添加到创建我的 Redux 存储的文件中:
export type AppDispatch = typeof store.dispatch;
export function useAppDispatch() {
return useDispatch<AppDispatch>();
}
然后在 src/features/posts/AddPostForm.tsx 中,我导入了 useAppDispatch
而不是 useDispatch
并使用了它。
我刚刚在尝试解决同样的问题时遇到了这个问题。我没有使用这些类型创建一个 useAppDispatch()
函数到 fiddle,而是意识到我来自 configureMiddleware 的存储不包括 thunk 类型作为调度操作。
在 this page,他们说使用中间件定义的回调将包括正确的类型。我曾经使用过的地方:
const store = configureStore({
reducer: rootReducer,
middleware: […getDefaultMiddleware(), logger]
})
将其更改为
const store = configureStore({
reducer: rootReducer,
middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(logger)
})
实际上解决了问题,因为 store.dispatch()
现在将 AsyncThunkAction
包含在可接受的参数类型列表中。这是一个更好的解决方案,但我希望它包含在关于 configureStore
.