TypeScript如何区分属性缺失与赋值undefined?限制函数仅接收单属性对象
TypeScript中区分属性缺失与显式赋值undefined的方案
你当前的写法之所以无法区分,是因为b?: never等价于b: never | undefined——TypeScript会把可选属性的类型默认加上undefined,所以显式传入b: undefined会被判定为符合类型约束。要实现你想要的校验,需要让类型严格禁止存在未预期的属性(哪怕是赋值为undefined的属性)。
下面给出两种可行的实现方案:
方案一:自定义严格联合类型
通过辅助类型让联合类型的每个成员都严格排斥其他成员的属性:
// 辅助类型:将联合类型转换为交叉类型 type UnionToIntersection<U> = (U extends unknown ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never; // 辅助类型:生成严格的联合类型,每个成员不能包含其他成员的属性 type StrictUnion<T> = T extends unknown ? T & Record<Exclude<keyof UnionToIntersection<T>, keyof T>, never> : never; // 定义基础接口 interface A { a: number; } interface B { b: number; } // 生成严格的参数类型 type StrictArg = StrictUnion<A | B>; const f = (arg: StrictArg) => {}; // ✅ 合法调用 f({ a: 0 }); // ❌ 报错:Type 'number' is not assignable to type 'never' f({ a: 0, b: 0 }); // ❌ 报错:Type 'undefined' is not assignable to type 'never' f({ a: 0, b: undefined });
这个方案的核心是StrictUnion类型:它先把联合类型转成交叉类型,获取所有可能的属性名,然后给每个联合成员加上约束——不属于该成员的其他属性必须是never类型,这样不管是传有效值还是undefined,只要属性存在就会触发类型错误。
方案二:使用satisfies运算符做局部校验
如果不想全局定义严格类型,可以用TypeScript 4.9+新增的satisfies运算符,在函数内部或参数层面做校验:
interface A { a: number; } interface B { b: number; } type Arg = A | B; const f = (arg: Arg) => { // 强制校验参数不能包含额外属性 const _ = arg satisfies Arg & Record<string, never>; }; // ✅ 合法调用 f({ a: 0 }); // ❌ 报错:类型不满足约束条件 f({ a: 0, b: 0 }); // ❌ 报错:类型不满足约束条件 f({ a: 0, b: undefined });
或者直接把校验逻辑写到参数类型里:
const f = <T extends Arg>( arg: T extends A ? Exclude<keyof T, keyof A> extends never ? T : never : T extends B ? Exclude<keyof T, keyof B> extends never ? T : never : never ) => {};
应用到React useReducer场景
把这个逻辑迁移到useReducer的action类型定义上,就能避免误传属性导致state被设为undefined:
type StrictUnion<T> = T extends unknown ? T & Record<Exclude<keyof UnionToIntersection<T>, keyof T>, never> : never; type UnionToIntersection<U> = (U extends unknown ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never; type State = { a: number; b: number }; type Action = StrictUnion< { type: 'UPDATE_A'; payload: { a: number } } | { type: 'UPDATE_B'; payload: { b: number } } >; const reducer = (state: State, action: Action): State => { switch (action.type) { case 'UPDATE_A': return { ...state, ...action.payload }; // 仅更新a,保留原有b的值 case 'UPDATE_B': return { ...state, ...action.payload }; // 仅更新b,保留原有a的值 default: return state; } }; // ✅ 合法调度 dispatch({ type: 'UPDATE_A', payload: { a: 10 } }); // ❌ 报错:不允许传入额外的b属性 dispatch({ type: 'UPDATE_A', payload: { a: 10, b: undefined } });
内容的提问来源于stack exchange,提问作者anotherOne
相关产品推荐
相关产品推荐

