You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.25 23:06:32