React/Redux/TS应用中如何正确为Fetch相关函数及Promise进行类型定义?
Hey there! That TypeScript error you're hitting makes total sense—right now, TypeScript can't confirm that your storyList is actually an array of IStory objects, so it's defaulting to unknown[]. Let's walk through fixing each function's type annotations step by step to clear this up.
Root Cause
The main issues are:
- Missing explicit return types on async functions, so TypeScript can't infer types correctly
checkResponsereturns an untyped value, leading to ambiguity- Error handling in some functions leaves room for unexpected
undefinedvalues in your story array
Step 1: Make checkResponse Generic
First, let's turn checkResponse into a generic function so it can return the correct type based on what we expect from the API:
const checkResponse = <T>(response: Response): Promise<T> => { if (response.ok) { return response.json() as Promise<T>; } return Promise.reject(new Error("Frontend error")); // Use Error objects instead of strings for better error handling };
This way, when we call checkResponse, we can explicitly tell TypeScript what type we expect the JSON to be.
Step 2: Fix fetchStoryIdList Type
Replace Promise<any> with the actual type we expect (number[]), and make sure we don't silently swallow errors (right now, your catch block logs but doesn't rethrow, which means the function could return undefined instead of an array):
const fetchStoryIdList = async (): Promise<number[]> => { try { const response = await fetch(`${BASE_URL}/newstories.json`); const idJson = await checkResponse<number[]>(response); return idJson; } catch (error) { console.error(error); throw error; // Re-throw so upstream functions can handle the error } };
Step 3: Fix fetchStoryList Type & Filter Invalid Entries
Add an explicit return type (Promise<IStory[]>), and filter out any undefined values that might come from failed API calls (since your catch block in the map returns nothing, which is undefined):
const fetchStoryList = async (): Promise<IStory[]> => { const idList = await fetchStoryIdList(); const promises = idList.slice(0, 100).map(async (id: number): Promise<IStory | undefined> => { try { const response = await fetch(`${BASE_URL}/item/${id}.json`); return checkResponse<IStory>(response); } catch (err) { console.error(`Failed to fetch story ${id}:`, err); return undefined; // Explicitly return undefined on failure } }); const storyList = await Promise.all(promises); // Filter out any undefined entries to ensure we only have valid IStory objects return storyList.filter((story): story is IStory => story !== undefined); };
The story is IStory type predicate tells TypeScript that after filtering, the array is definitely IStory[].
Step 4: Update fetchStories to Match
Now TypeScript will correctly infer that storyList is IStory[], so the dispatch error will disappear. We can also add a bit more type safety to the thunk:
const fetchStories = () => { return async (dispatch: Dispatch<StoryActions>) => { try { dispatch(fetchStoryAction()); const storyList = await fetchStoryList(); // No more error here! TypeScript knows storyList is IStory[] dispatch(fetchStorySuccessAction(storyList)); } catch (error) { console.error("Failed to fetch stories:", error); // Optional: Dispatch an error action here too // dispatch(fetchStoryErrorAction(error instanceof Error ? error.message : "Unknown error")); } }; };
Full Corrected Code Snippet
Putting it all together, here's the updated code with proper type annotations:
export interface IStory { by: string; descendants: number; id: number; kids: number[]; score: number; time: number; title: string; type: string; url: string; } export interface IStoryState { storyList: IStory[]; storyError: null | string; isStoryLoading: boolean; } // Assume StoryActionTypes and StoryActions are defined correctly export const fetchStorySuccessAction = (storyList: IStory[]): IFetchStoryDataSuccessAction => { return { type: StoryActionTypes.FETCH_STORY_DATA_SUCCESS, payload: storyList, }; }; const checkResponse = <T>(response: Response): Promise<T> => { if (response.ok) { return response.json() as Promise<T>; } return Promise.reject(new Error("Frontend error")); }; const fetchStoryIdList = async (): Promise<number[]> => { try { const response = await fetch(`${BASE_URL}/newstories.json`); const idJson = await checkResponse<number[]>(response); return idJson; } catch (error) { console.error(error); throw error; } }; const fetchStoryList = async (): Promise<IStory[]> => { const idList = await fetchStoryIdList(); const promises = idList.slice(0, 100).map(async (id: number): Promise<IStory | undefined> => { try { const response = await fetch(`${BASE_URL}/item/${id}.json`); return checkResponse<IStory>(response); } catch (err) { console.error(`Failed to fetch story ${id}:`, err); return undefined; } }); const storyList = await Promise.all(promises); return storyList.filter((story): story is IStory => story !== undefined); }; const fetchStories = () => { return async (dispatch: Dispatch<StoryActions>) => { try { dispatch(fetchStoryAction()); const storyList = await fetchStoryList(); dispatch(fetchStorySuccessAction(storyList)); } catch (error) { console.error("Failed to fetch stories:", error); } }; };
Key Takeaways
- Use generic functions for API response parsing to keep types consistent
- Always add explicit return types to async functions (avoid
anylike the plague!) - Handle edge cases like failed API calls by filtering out invalid entries
- Re-throw errors in lower-level functions so upstream code can handle them properly
内容的提问来源于stack exchange,提问作者qweezz

