React TypeScript:如何让API调用结果在多组件中复用
React/TypeScript 跨组件共享API数据的标准实现方式
首先明确:你可以把API调用逻辑抽离到单独文件夹统一管理,但不能直接导入请求结果——因为API请求是异步操作,你导出的只能是请求函数,不是已经拿到的现成数据。至于要不要重复发起请求,完全可以通过方案避免,下面是几种行业内的标准实现方式:
1. 先把API请求逻辑抽离到单独模块
第一步先把所有API请求代码集中到一个地方,比如创建src/services/api.ts:
// src/services/api.ts // 定义数据类型(TypeScript必备) type User = { id: number; name: string; email: string; }; // 封装API请求函数 export const fetchUserInfo = async (): Promise<User> => { const response = await fetch('https://your-api-domain.com/user/profile'); if (!response.ok) { throw new Error(`请求失败:${response.status}`); } return response.json(); };
这样所有需要调用这个接口的组件,都可以导入这个函数,不用重复写请求逻辑。但直接在多个组件里调用fetchUserInfo()的话,每个组件都会发起独立请求,这时候就需要结合状态管理或缓存来复用数据。
2. 避免重复请求的核心方案
方案一:React Context + useReducer(轻量全局状态)
把请求到的数据存在全局Context里,所有组件直接从Context读取,整个应用只请求一次。
示例代码:
// src/contexts/UserContext.tsx import { createContext, useContext, useEffect, useReducer } from 'react'; import { fetchUserInfo, User } from '../services/api'; // 定义状态类型 type UserState = { data: User | null; loading: boolean; error: string | null; }; // 定义操作类型 type UserAction = | { type: 'START_LOADING' } | { type: 'FETCH_SUCCESS'; payload: User } | { type: 'FETCH_ERROR'; payload: string }; // 初始状态 const initialState: UserState = { data: null, loading: false, error: null, }; // 状态更新器 const userReducer = (state: UserState, action: UserAction): UserState => { switch (action.type) { case 'START_LOADING': return { ...state, loading: true, error: null }; case 'FETCH_SUCCESS': return { ...state, loading: false, data: action.payload }; case 'FETCH_ERROR': return { ...state, loading: false, error: action.payload }; default: return state; } }; // 创建Context const UserContext = createContext<{ state: UserState } | undefined>(undefined); // 提供状态的Provider组件 export const UserProvider = ({ children }: { children: React.ReactNode }) => { const [state, dispatch] = useReducer(userReducer, initialState); // 组件挂载时请求一次数据 useEffect(() => { const loadUserInfo = async () => { dispatch({ type: 'START_LOADING' }); try { const userData = await fetchUserInfo(); dispatch({ type: 'FETCH_SUCCESS', payload: userData }); } catch (err) { dispatch({ type: 'FETCH_ERROR', payload: (err as Error).message }); } }; loadUserInfo(); }, []); return ( <UserContext.Provider value={{ state }}> {children} </UserContext.Provider> ); }; // 自定义Hook,方便组件获取状态 export const useUser = () => { const context = useContext(UserContext); if (!context) { throw new Error('useUser必须在UserProvider组件内部使用'); } return context.state; };
然后在根组件App.tsx里包裹所有需要共享数据的组件:
// src/App.tsx import { UserProvider } from './contexts/UserContext'; import ProfileCard from './components/ProfileCard'; import UserSettings from './components/UserSettings'; function App() { return ( <UserProvider> <ProfileCard /> <UserSettings /> </UserProvider> ); } export default App;
最后在组件里直接使用:
// src/components/ProfileCard.tsx import { useUser } from '../contexts/UserContext'; export default function ProfileCard() { const { data, loading, error } = useUser(); if (loading) return <div>加载中...</div>; if (error) return <div>出错了:{error}</div>; return ( <div className="profile-card"> <h2>{data?.name}</h2> <p>{data?.email}</p> </div> ); }
这样ProfileCard和UserSettings都会共享同一份用户数据,不会重复请求。
方案二:使用SWR/React Query(专业数据缓存库)
如果你的项目涉及大量API请求、需要缓存过期、自动重试等复杂逻辑,直接用这类成熟库更省心,它们内置了缓存、去重、背景刷新等功能。
以SWR为例,先安装依赖:
npm install swr
然后组件里直接使用:
// src/components/ProfileCard.tsx import useSWR from 'swr'; import { fetchUserInfo, User } from '../services/api'; export default function ProfileCard() { // 第一个参数是缓存键,相同键会复用缓存 const { data, error, isLoading } = useSWR<User>('user-profile', fetchUserInfo); if (isLoading) return <div>加载中...</div>; if (error) return <div>出错了:{error.message}</div>; return ( <div className="profile-card"> <h2>{data?.name}</h2> <p>{data?.email}</p> </div> ); }
另一个组件UserSettings用同样的缓存键调用,就会直接用缓存数据,不会发起新请求:
// src/components/UserSettings.tsx import useSWR from 'swr'; import { fetchUserInfo, User } from '../services/api'; export default function UserSettings() { const { data, error, isLoading } = useSWR<User>('user-profile', fetchUserInfo); if (isLoading) return <div>加载中...</div>; if (error) return <div>出错了:{error.message}</div>; return ( <div className="settings"> <label>用户名:<input defaultValue={data?.name} /></label> <label>邮箱:<input defaultValue={data?.email} /></label> </div> ); }
方案三:Props层级传递(仅适用于简单场景)
如果数据只在少数几个层级浅的组件间共享,也可以在最上层的父组件里请求数据,然后通过props传递给子组件。但如果组件层级很深,这种方式会导致“props drilling”(层层传递props),代码维护性很差,不推荐复杂场景。
总结
- API逻辑一定要抽离:统一放在
services或api文件夹,方便后续修改、添加拦截器等操作; - 不能直接导入请求结果:异步请求的结果是动态获取的,你只能导入请求函数;
- 避免重复请求的标准选择:
- 轻量小项目:React Context + useReducer;
- 中大型项目或复杂API场景:SWR/React Query;
- 极简单的层级场景:Props传递。
内容的提问来源于stack exchange,提问作者jm22b
相关产品推荐
相关产品推荐

