如何优雅地将TypeScript类型联合的单个分支设为必选?
问题背景与场景
我们的应用里定义了两个Action子类型:
export interface QueuedAction extends Action { meta: { queueName: string; overwrite: boolean; } } export interface BroadcastAction extends Action { meta: { channelName: string; } }
参考"typescript interface require one of two properties to exist"的思路,将它们合并为联合类型:
export type MetaAction = QueuedAction | BroadcastAction;
但在处理需要确保联合类型某一分支存在的场景时,当前写法出现了类型错误:
const wrapBroadcast = (action: AnyAction, channelName = 'DEFAULT_CHANNEL'): MetaAction => ({ ...action, meta: { ...action.meta, channelName }, }); const unwrapBroadcast = (action: MetaAction) => { if (!action.meta) return action; const { channelName, ...rest } = action.meta; // 类型错误:channelName 不存在于 '{ queueName: string, overwrite?: boolean} | { channelName: string }' const unwrappedAction = { ...action, meta: rest }; return unwrappedAction; }
尝试改用BroadcastAction | (BroadcastAction & MetaAction)时,又出现类型不兼容错误:
Argument of type 'MetaAction' is not assignable to parameter of type 'BroadcastAction & MetaAction'
仅修改wrapAction的返回类型可以临时解决问题,但未来meta字段会扩展,且该模式需要复用,请问有没有更通用的类型声明方式?
通用解决方案
可以通过类型守卫和通用类型工具来解决问题,既保证类型安全,又能支持未来的字段扩展:
1. 定义类型守卫函数
先实现一个类型守卫,用来在运行时区分MetaAction的分支:
function isBroadcastAction(action: MetaAction): action is BroadcastAction { return (action.meta as BroadcastAction['meta']).channelName !== undefined; }
2. 改造unwrapBroadcast函数
利用类型守卫精准处理BroadcastAction分支,避免类型错误:
const unwrapBroadcast = (action: MetaAction) => { if (!action.meta) return action; // 确认是BroadcastAction分支后再解构channelName if (isBroadcastAction(action)) { const { channelName, ...rest } = action.meta; return { ...action, meta: rest }; } // 非BroadcastAction分支直接返回原对象 return action; }
3. 优化wrapBroadcast的类型声明
用泛型结合条件类型,让返回类型更精准,同时支持未来meta字段的扩展:
type WrapBroadcast<T extends AnyAction> = T extends { meta: infer M } ? Omit<T, 'meta'> & { meta: M & { channelName: string } } : T & { meta: { channelName: string } }; const wrapBroadcast = <T extends AnyAction>(action: T, channelName = 'DEFAULT_CHANNEL'): WrapBroadcast<T> & MetaAction => ({ ...action, meta: { ...action.meta, channelName }, });
4. 通用分支提取工具类型(可选)
如果未来需要处理更多联合类型分支,可以定义通用工具类型,提取包含指定meta属性的分支:
type ExtractByMetaProp<T, K extends string> = T extends { meta: Record<K, any> } ? T : never; // 使用示例:提取MetaAction中包含channelName的分支 type OnlyBroadcast = ExtractByMetaProp<MetaAction, 'channelName'>; // 等价于BroadcastAction
这种写法既解决了当前的类型错误,又能应对未来meta字段的扩展,同时保证了类型系统的严谨性。
内容的提问来源于stack exchange,提问作者AncientSwordRage
相关产品推荐
相关产品推荐

