TypeScript泛型判别联合类型:能否避免双重检查判别器?
如何避免在Reducer中重复检查state与action.payload的判别器?
首先看你定义的类型结构:
interface BaseState { isError: boolean; errorMessage: string; } interface IPState extends BaseState { ip: string; type: 'ip'; } interface LocationState extends BaseState { city: string; countryCode: string; type: 'location'; } interface TimezoneState extends BaseState { timezone: string; type: 'timezone'; } type DialogState = IPState | LocationState | TimezoneState; enum DialogActionType { ERROR = 'ERROR', DATA = 'DATA', } interface DialogAction<T extends DialogState> { type: DialogActionType; payload: T; }
以及简化后的Reducer:
export default function dialogReducer<T extends DialogState>( state: T, action: DialogAction<T>, ) { switch (action.type) { case DialogActionType.DATA: if (state.type === 'ip') { return { ...state, ip: action.payload.ip, // 此处TypeScript无法自动收窄payload类型 }; } // 其他case逻辑... } }
目前的问题是:检查state.type后,state能被正确收窄为对应类型,但action.payload无法同步收窄,必须额外检查action.payload.type才能通过类型校验。以下是几种无需重复检查的解决方案:
方案1:将DialogAction改为带判别式的联合类型
放弃泛型接口,直接定义与DialogState严格绑定的联合类型Action,让TypeScript自动关联payload的类型:
// 自动生成对应每个state类型的DATA Action type DataAction = { [K in DialogState['type']]: { type: DialogActionType.DATA; payload: Extract<DialogState, { type: K }>; } }[DialogState['type']]; // 单独定义ERROR Action type ErrorAction = { type: DialogActionType.ERROR; payload: BaseState; }; // 合并为最终的DialogAction类型 type DialogAction = DataAction | ErrorAction;
修改后的Reducer无需泛型,仅通过检查state.type即可让TypeScript自动推断action.payload的类型:
export default function dialogReducer( state: DialogState, action: DialogAction, ) { switch (action.type) { case DialogActionType.DATA: if (state.type === 'ip') { return { ...state, ip: action.payload.ip, // 类型自动收窄为IPState,无报错 }; } if (state.type === 'location') { return { ...state, city: action.payload.city, countryCode: action.payload.countryCode, }; } // 处理ERROR case... } return state; }
方案2:用类型断言辅助收窄(简易但需手动保证安全)
如果不想修改Action的类型定义,可以在检查state.type后,对action.payload进行类型断言:
export default function dialogReducer<T extends DialogState>( state: T, action: DialogAction<T>, ) { switch (action.type) { case DialogActionType.DATA: if (state.type === 'ip') { const ipPayload = action.payload as IPState; return { ...state, ip: ipPayload.ip, }; } // 其他case逻辑... } return state; }
注意:这种方式需要你手动保证state.type与payload.type的一致性,若业务逻辑出错可能导致运行时问题。
方案3:通过泛型约束强化state与payload的关联
调整Reducer的泛型参数,强制state和action.payload的type完全匹配:
// 根据type提取对应的DialogState子类型 type DialogStateByType<T extends DialogState['type']> = Extract<DialogState, { type: T }>; export default function dialogReducer<T extends DialogState['type']>( state: DialogStateByType<T>, action: { type: DialogActionType.DATA; payload: DialogStateByType<T> } ) { switch (action.type) { case DialogActionType.DATA: if (state.type === 'ip') { return { ...state, ip: action.payload.ip, // 类型自动匹配,无需额外检查 }; } // 其他case逻辑... } return state; }
这种方式通过泛型约束从根源保证了state和payload的类型一致性,TypeScript能自动完成类型推断。
内容的提问来源于stack exchange,提问作者kaaaxcreators
相关产品推荐
相关产品推荐

