TypeScript中Pick<T, never>返回any而非空对象的解决方法问询
问题描述
我发现TypeScript中空对象相关的类型行为完全不一致,比如这段代码居然不报错:
const empty = {} as const; const xx: typeof empty = { ho: 'hi' }
但下面这段就会报错:
const notEmpty = { hasKey: 'andValue' } as const; const yy: typeof notEmpty = { ho: 'hu' // 提示不可赋值 }
相关问题已有官方Issue,但未被重视。
我正基于基础输入对象构建包含各类属性与函数的复杂API对象,用到映射类型、条件类型等语法,但遇到严重问题:嵌套工具类型最终得到等效Pick<T, never>的类型时,按预期应返回空对象类型,实际却返回any,完全破坏了类型安全。
比如这段简化代码:
type Original = { ho: string; ha: string; hu: string; } type Derived = Pick<Original, 'ho'>; const thisIsFine: Derived = { // 正常工作,符合预期 ho: 'yes', // ha: '无法赋值' } type ShouldBeEmptyObject = Pick<Original, never>; const shouldntBePossible: ShouldBeEmptyObject = { anythingGoes: "这完全不安全" }
更实际的场景中,我通过判断键是否以$开头区分参数和属性:
type AnyObject = { [key: string]: any } type ParameterSegments<T extends AnyObject> = Pick<T, Extract<keyof T, `$${string}`>>; type PropertySegments<T extends AnyObject> = Omit<T, Extract<keyof T, `$${string}`>>; const inputObject = { property1: 'hoho', property2: 'haha', $parameter7: 'param' }; const onlyProperties: PropertySegments<typeof inputObject> = { // 正常工作 property1: 'juppie', property2: 'juchei', // property3: "无法赋值,不在inputObject里", // $parameter7: "无法赋值,属于参数" }; const onlyPropertyInput = { propertyX: 'XxX' }; const onlyParameters: ParameterSegments<typeof onlyPropertyInput> = { // 完全失控,任何属性都能添加 jap: 'no', $worries: '!', anything: 'goes', $nothing: 'isRequired' };
请问这是Bug还是无法避免的特性?如何解决该问题?
解决方案与分析
这是TypeScript的历史特性而非Bug
TypeScript中,Pick<T, never>会返回{}类型,但{}并非严格的空对象类型——它表示任何非null/undefined的值,包括带额外属性的对象。当Pick的结果结合索引签名(比如你的AnyObject包含[key: string]: any),最终类型会被简化为any,这是TS类型推导的历史遗留设计行为。
解决方法:定义严格的空对象类型
要实现严格禁止额外属性的空对象类型,需手动定义精确空对象类型,替代Pick<T, never>的默认行为:
// 严格空对象类型:只能是空对象,不能有任何额外属性 type StrictEmptyObject = { [K in never]: never }; // 重写ParameterSegments,用StrictEmptyObject兜底 type ParameterSegments<T extends AnyObject> = Extract<keyof T, `$${string}`> extends never ? StrictEmptyObject : Pick<T, Extract<keyof T, `$${string}`>>;
修改后,onlyParameters的类型会变为StrictEmptyObject,此时添加任何属性都会触发类型错误,符合预期。
补充:关于const empty = {} as const的行为
{} as const得到的类型是readonly {},TS的结构类型系统允许子类型赋值给父类型,带额外属性的对象属于{}的子类型,因此不会报错。如果要严格限制,直接用StrictEmptyObject约束即可:
const xx: StrictEmptyObject = { ho: 'hi' }; // 直接报错,符合预期
内容的提问来源于stack exchange,提问作者NoBullsh1t
相关产品推荐
相关产品推荐

