如何在TypeScript中从联合类型API响应动态推断属性?
解决方案:让TypeScript自动识别React Query onSuccess中的响应类型
问题背景
我正在开发一个TypeScript项目,使用React Query的useMutation处理登录流程,API返回的响应是联合类型LoginResponse:
type LoginResponse = { success: boolean; message: string; } | { success: boolean; message: string; } | { success: boolean; message: string; accessToken: string; }
当响应状态码为401/403时,仅返回success和message,此时loginFunction会抛出错误触发onError回调;状态码为201时返回包含accessToken的完整响应,进入onSuccess回调。现在希望在onSuccess中让TypeScript自动识别data包含accessToken属性,无需硬编码属性名进行检查。
方法一:精确化异步函数的返回类型
由于错误响应已经被loginFunction抛出,onSuccess接收的data必然是包含accessToken的响应分支。我们可以用TypeScript的Extract工具类型从联合类型中提取该分支,作为loginFunction的返回类型,同时添加可选的运行时验证确保响应符合预期:
const loginFunction = async ( credentials: LoginRequest ): Promise<Extract<LoginResponse, { accessToken: string }>> => { const res = await login(credentials); if (!res.ok) throw new Error("Failed to login"); const json = await res.json(); // 可选运行时验证:确保成功响应包含accessToken if (!('accessToken' in json)) { throw new Error("Invalid success response: missing accessToken"); } return json; };
修改后,useMutation的成功类型会自动推断为带accessToken的分支,onSuccess中的data会被TypeScript自动识别为包含该属性的类型,直接使用即可:
export const useLoginMutation = () => { const navigate = useNavigate(); const mutation = useMutation({ mutationFn: loginFunction, mutationKey: ["auth", "login"], onSuccess: (data) => { // TS自动识别data.accessToken存在 localStorage.setItem('authToken', data.accessToken); navigate('/dashboard'); }, onError: (error) => { console.error('Login failed:', error.message); }, }); return mutation; };
方法二:通用类型守卫(适用于动态属性检查)
如果需要在其他场景中动态检查任意属性而不硬编码属性名,可以创建一个通用的类型守卫函数:
// 通用类型守卫:检查对象是否包含指定属性 function hasOwnProperty<T, K extends string>(obj: T, prop: K): obj is T & Record<K, unknown> { return typeof obj === 'object' && obj !== null && Object.prototype.hasOwnProperty.call(obj, prop); }
在onSuccess中使用该守卫,TypeScript会自动缩小类型范围:
onSuccess: (data) => { if (hasOwnProperty(data, 'accessToken')) { // 此处data被TS识别为包含accessToken的类型 localStorage.setItem('authToken', data.accessToken); navigate('/dashboard'); } }
不过在登录场景中,方法一更高效,因为它利用了错误响应已被抛出的前提,让TypeScript直接推断出精确类型,无需额外检查。
内容的提问来源于stack exchange,提问作者Nazar Duma
相关产品推荐
相关产品推荐

