Link to playground with an example.

我有一个复杂的接口,它有两个属性:

  • 一个attributes人的数组
  • 记录initialValues,其中关键字必须来自属性数组(但不是所有属性,因此它应该是部分的)
export interface Data<
  TAttributes extends string,
> {
  readonly attributes?: readonly TAttributes[]
  readonly initialValues?: {
    readonly [TKey in TAttributes & {}]?: string
  }
}

当我的对象只是一个普通对象时,它可以工作,当initialValues声明不是attributes一部分的键时,我会得到一个错误.

function data<TAttributes extends string>(data: Data<TAttributes>): Data<TAttributes> {
  return data
}

const okay = data({
  attributes: ['firstName'],
  initialValues: {
    firstName: 'value',
    age: 2 // error here - which is correct
  },
})

但当我的参数实际上是返回那种对象(Data)的函数时-它不会.当添加不同的属性名而不是attributes的一部分时,由于奇怪的失败推理,推断出的额外属性的类型是any.

function dataFunc<TAttributes extends string>(data: () => Data<TAttributes>): () => Data<TAttributes> {
  return data
}

const notOkay = dataFunc(() => ({
  attributes: ['firstName'],
  initialValues: {
    firstName: 'value',
    age: 2 // should error out, but it doesn't
  },
}))

example

有没有办法严格执行这一点,或者:

  • 部分记录中的精确键(我知道TypeScrip不正式支持这一点,但也许有一种方法可以在这个特定用例中破解它).

  • 改进函数参数的推理.我试过用NoInfer技巧来做,但失败了.

  • 不允许一条记录中有any个值.通常这是通过linting规则和typescript-eslint插件(例如no-unsafe-return)来完成的,但没有针对对象值的规则.另外,我希望这是一张打字支票,而不是皮棉支票.

Link to the playground.

推荐答案

一般而言,TypeScrip并不能真正禁止多余的属性;对象类型是开放和可扩展的,而不是封闭或密封的(参见microsoft/TypeScript#12936).无论你做什么,都不会阻止你这样做:

const a = { firstName: 'value', age: 2 }; // okay
const b: { firstName: string } = a; // okay
dataFunc(() => ({ attributes: ['firstName'], initialValues: b })) // okay

在TypeScrip中,我们最多只能处理discourage个额外属性.您在okay示例中看到的data的错误是excess property checking的示例,但这只对直接分配给期望较少属性的位置的对象文字起作用.它不适用于函数返回(请参见microsoft/TypeScript#241),这就是为什么您在那里没有得到错误的原因.

如果我们想要在dataFunc()中模拟多余的属性判断,我们需要将其设置为generic以捕获initialValues属性的完整类型,然后将constrain该类型设置为其中任何多余属性的属性类型为the impossible never type:

type ExactAttributes<T, K extends PropertyKey> = {
  [P in keyof T]: P extends K ? unknown : never
}   

function dataFunc<
  K extends string,
  T extends ExactAttributes<T, K>
>(data: () =>
  Data<K> & { initialValues?: T }): () => Data<K> {
  return data
}

这一点的一部分是不变的,你仍然有K extends stringdata‘S返回类型仍然是Data<K>,但现在我们说它是also {initValues?: T},其中T被约束为ExactAttributes<T, K>.如果T{firstName: string, age: string},K"firstName",那么ExactAttributes<T, K>{firstName: unknown, age: never}.因此T extends ExactActributes<T, K>不是真的,因为age是类型string而不是never.因此,您将获得所需的错误:

const notOkay = dataFunc(() => ({
  attributes: ['firstName'],
  initialValues: {
    firstName: 'value',
    age: 2 // error
    // Type 'number' is not assignable to type 'never'.
  }
}))

这样,您就有了类似于判断函数返回类型的额外属性的内容.但请记住,多余的属性并不真的被认为是类型错误,因此该语言并不真的想帮助您强制执行这类事情.

Playground link to code

Typescript相关问答推荐

类型脚本:类型是通用的,只能索引以供阅读.(2862)"

Angular 17 Select 错误core.mjs:6531错误错误:NG 05105:发现意外合成监听器@transformPanel.done

更新为Angular 17后的可选链运算符&?&问题

等待用户在Angular 函数中输入

从TypeScrip中的其他类型检索泛型类型

错误TS2403:后续变量声明必须具有相同的类型.变量';CRYPTO';的类型必须是';CRYPATO';,但这里的类型是';CRYPATO';

CDK从可选的Lambda角色属性承担角色/复合主体中没有非空断言

编译TypeScrip项目的一部分导致错误

从非文字数组(object.keys和array.map)派生联合类型

如何将spread operator与typescripts实用程序类型`参数`一起使用

如何键入派生类的实例方法,以便它调用另一个实例方法?

在打字应用程序中使用Zod和Reaction-Hook-Forms时显示中断打字时出错

TypeScrip:从嵌套的对象值创建新类型

替换typescript错误;X类型的表达式可以';t用于索引类型Y;带有未定义

在tabel管理区域react js上设置特定顺序

如何在TypeScript类成员函数中使用筛选后的keyof this?

类型分布在第一个泛型上,但不分布在第二个泛型上

为什么过滤器不改变可能的类型?

为什么我的 Typescript 函数中缺少 void 隐式返回类型?

基于泛型类型的条件类型,具有条目属性判断