TypeScript提示属性不存在但实际已定义的类型问题排查
TypeScript联合类型收窄失败:访问属性报错问题排查与修复
问题描述
定义了BackendError泛型联合类型:当provider为'email'时包含errors属性,其余provider(facebook/google/linkedin)时包含error属性。但在组件中通过backendError?.provider === 'email'进行类型收窄后,访问backendError.errors仍被TypeScript提示“属性'errors'不存在”,访问backendError.error时也有类似报错。
组件代码
import { type IResolveParams } from 'reactjs-social-login'; import { Box, Divider } from '@chakra-ui/react'; import { EmailSignupForm, type EmailSignupDataSchema } from './EmailSignupForm'; import { SocialSignups } from './SocialSignups'; import { SignupLoading } from './SignupLoading'; type SignupProvider = 'email' | 'facebook' | 'google' | 'linkedin'; type SignupData<T extends SignupProvider> = T extends 'email' ? { provider: 'email'; data: EmailSignupDataSchema; } : IResolveParams; type BackendError<T extends SignupProvider> = T extends 'email' ? { provider: 'email'; errors: Partial<EmailSignupDataSchema>; } : { provider: Omit<SignupProvider, 'email'>; error: string; }; type SignupProps = { onSubmit: (data: SignupData<SignupProvider>) => void; isLoading?: boolean; backendError?: BackendError<SignupProvider>; }; export const Signup = ({ onSubmit, isLoading, backendError, }: SignupProps): JSX.Element => { const onEmailSignupFormSubmit = (data: EmailSignupDataSchema): void => { onSubmit({ provider: 'email', data, }); }; return ( <Box minW={{ base: '90%', md: '468px' }} backgroundColor="whiteAlpha.900" boxShadow="md" px={5} py={8} position="relative" > {isLoading === true ? <SignupLoading /> : null} <EmailSignupForm onSubmit={onEmailSignupFormSubmit} backendError={ backendError?.provider === 'email' ? backendError.errors : undefined } /> <Divider /> <SocialSignups onSubmit={onSubmit} backendError={ backendError?.error != null ? backendError.error : undefined } /> </Box> ); };
报错信息
Property 'errors' does not exist on type '{ provider: "email"; errors: Partial<{ email: string; password: string; confirmPassword: string; captchaToken: string; }>; } | { provider: Omit<SignupProvider, "email">; error: string; }'. Property 'errors' does not exist on type '{ provider: Omit<SignupProvider, "email">; error: string; }'.
访问backendError.error时会出现类似的“属性不存在”报错。
问题根源
问题出在泛型条件类型的分发与可辨识联合类型的定义错误:
- 当使用
BackendError<SignupProvider>时,泛型T是联合类型SignupProvider,条件类型会对联合类型的每个成员进行分发,最终生成的类型是BackendError<'email'> | BackendError<'facebook'> | BackendError<'google'> | BackendError<'linkedin'>; - 但
BackendError<Omit<SignupProvider, 'email'>>(非email分支)返回的类型是{ provider: Omit<SignupProvider, 'email'>; error: string; },这里的provider是联合类型,而非单个字面量类型; - TypeScript的可辨识联合类型要求判别式(此处为
provider)必须是字面量类型或字面量类型的联合,且每个分支的判别式值唯一绑定对应的属性。由于非email分支的provider是联合类型,TypeScript无法通过provider === 'email'准确收窄到对应的分支类型,导致属性访问报错。
修复方案
将BackendError从泛型类型改为直接定义的可辨识联合类型,确保每个分支的provider是具体的字面量类型,让TypeScript能正确识别判别式:
修改后的类型定义
type SignupProvider = 'email' | 'facebook' | 'google' | 'linkedin'; // 提取非email的provider类型 type SocialProvider = Exclude<SignupProvider, 'email'>; // 直接定义可辨识联合类型,无需泛型 type BackendError = | { provider: 'email'; errors: Partial<EmailSignupDataSchema>; } | { provider: SocialProvider; error: string; }; // 更新SignupProps中的backendError类型 type SignupProps = { onSubmit: (data: SignupData<SignupProvider>) => void; isLoading?: boolean; backendError?: BackendError; // 去掉泛型参数 };
修复原理
修改后的BackendError是标准的可辨识联合类型:
- 第一个分支的
provider是字面量'email',绑定errors属性; - 第二个分支的
provider是SocialProvider('facebook'|'google'|'linkedin'),绑定error属性;
TypeScript能通过backendError?.provider === 'email'准确推断出当前类型是第一个分支,允许访问errors;反之则推断为第二个分支,允许访问error。
内容的提问来源于stack exchange,提问作者Daniel Kormos
相关产品推荐
相关产品推荐

