TypeScript中使用常量定义接口类型时Switch访问data报错
问题重现
在TypeScript接口中使用常量对象APP_STATUS定义联合类型的type判别字段后,React组件的switch语句中无法正确收窄类型,访问viewState.data时出现错误:Property 'data' does not exist on type 'ViewState'。但直接使用字符串字面量(如'INCONSISTENCIES')定义type字段时,类型收窄正常。
相关代码如下:
types.ts
export const APP_STATUS = { CONFIRMED: 'CONFIRMED', INCONSISTENCIES: 'INCONSISTENCIES', SUCCESS: 'SUCCESS', ERROR: 'ERROR', LOADING: 'LOADING', OK: 'OK' } interface InconsistenciesLoading { type: typeof APP_STATUS.LOADING; } interface InconsistenciesError { type: typeof APP_STATUS.ERROR; } interface InconsistenciesSuccess { type: typeof APP_STATUS.SUCCESS; } interface InconsistenciesData { type: typeof APP_STATUS.INCONSISTENCIES; data: Inconsistency[]; } export type ViewState = InconsistenciesData | InconsistenciesSuccess | InconsistenciesLoading | InconsistenciesError;
React组件
const [viewState, setViewState] = useState<ViewState>({type: APP_STATUS.LOADING}) const renderPageContent = () => { switch (viewState.type) { case APP_STATUS.INCONSISTENCIES: return <InconsistenciesTable inconsistencies={viewState.data} /> // 报错:Property 'data' does not exist on type 'ViewState' case APP_STATUS.ERROR: return <Forbidden /> case APP_STATUS.SUCCESS: return <ThankYouContent /> case APP_STATUS.LOADING: return <Loading /> } }
问题原因
TypeScript对普通const对象的属性默认会推断为宽泛的string类型,而非具体的字符串字面量类型。例如typeof APP_STATUS.INCONSISTENCIES会被推断为string,而不是'INCONSISTENCIES'。这导致联合类型的判别式失效,switch语句无法根据type字段将viewState收窄到对应的子类型(如InconsistenciesData),因此TypeScript无法确认data属性存在。
解决方案
方案1:给常量对象添加as const断言
通过as const强制TypeScript将对象的每个属性推断为对应的字符串字面量类型:
export const APP_STATUS = { CONFIRMED: 'CONFIRMED', INCONSISTENCIES: 'INCONSISTENCIES', SUCCESS: 'SUCCESS', ERROR: 'ERROR', LOADING: 'LOADING', OK: 'OK' } as const;
修改后,typeof APP_STATUS.INCONSISTENCIES的类型会变成'INCONSISTENCIES',联合类型的判别式恢复正常,switch语句中case APP_STATUS.INCONSISTENCIES分支会自动将viewState收窄为InconsistenciesData,此时访问data属性不再报错。
方案2:使用枚举(enum)替代常量对象
枚举的成员本身就是字面量类型,天然支持类型收窄:
export enum APP_STATUS { CONFIRMED = 'CONFIRMED', INCONSISTENCIES = 'INCONSISTENCIES', SUCCESS = 'SUCCESS', ERROR = 'ERROR', LOADING = 'LOADING', OK = 'OK' }
接口中的type字段直接使用枚举成员即可:
interface InconsistenciesLoading { type: APP_STATUS.LOADING; } // 其他接口同理
方案3:定义字面量类型别名并约束常量对象
先定义包含所有状态的字面量类型别名,再用该类型约束常量对象:
type AppStatus = 'CONFIRMED' | 'INCONSISTENCIES' | 'SUCCESS' | 'ERROR' | 'LOADING' | 'OK'; export const APP_STATUS: Record<AppStatus, AppStatus> = { CONFIRMED: 'CONFIRMED', INCONSISTENCIES: 'INCONSISTENCIES', SUCCESS: 'SUCCESS', ERROR: 'ERROR', LOADING: 'LOADING', OK: 'OK' };
这种方式同样能让TypeScript推断出每个属性的字面量类型,实现类型收窄。
内容的提问来源于stack exchange,提问作者Tushar kharat

