【问题标题】:Passing property names to type properties将属性名称传递给类型属性
【发布时间】:2021-11-30 21:25:45
【问题描述】:

我正在尝试键入一个其属性具有任意名称的配置对象。 每个任意键名都必须有一个值,该值是一个类型化对象,具有一些已定义的属性,该属性具有作为值的父对象的键名之一。

这是我想出的:

//parent type
type ParentType<KeyNames extends string> = {
  [k in KeyNames]: ChildType<KeyNames>;
};

//child type
interface ChildType<KeyNames extends string>  {
  oneOfKeys : KeyNames
}

问题是它的工作方向与我希望的相反。


function testFunc<Keynames extends string>(foo:ParentType<Keynames>){
}

//testFunc is infered as testFunc<"bar" | "baz" | "foo">
//i would like for it to be infered as testFunc<"foo" | "bar" | "bal">
testFunc({
  foo: {
    oneOfKeys: "bar"
  },
  bar : {
    oneOfKeys: "baz"
  },
  bal : { //TS is complaining about the "bal" key
    oneOfKeys: "foo"
  }
})

我希望编译器检查 oneOfkeys 是父对象的键之一,但它检查父键是否是 oneOfKeys 的值之一。 当我希望它自上而下地工作时,就像泛型的推理是自下而上地工作一样。

我尝试了其他各种根本不起作用的方法。

【问题讨论】:

  • 能否请您澄清您期望错误的位置以及您不期望的位置。应该验证oneOfKeys 吗?
  • @captain-yossarian foo、bar 和 bal 键应该被接受,但 "oneOfKeys" : "baz" 应该被拒绝,因为没有父 "baz" 键。

标签: typescript type-inference typescript-generics


【解决方案1】:

将整个对象传递给ParentType 意味着编译器不会错误地推断出错误的键:

type ParentType<O> = {
  [k in keyof O]: ChildType<O>;
};

//child type
interface ChildType<O>  {
  oneOfKeys : keyof O
}

function testFunc<O>(foo:ParentType<O>){
}


testFunc({
  foo: {
    oneOfKeys: "bar"
  },
  bar : {
    oneOfKeys: "baz" // Error
  },
  bal : {
    oneOfKeys: "foo"
  }
})

【讨论】:

  • 谢谢,但现在的问题是,由于对 O 没有约束,所以 foo:ParentType 被推断为一大袋未知数。对我的对象结构的约束是强制的,但我以后不能用它做任何事情。
【解决方案2】:

首先你需要推断函数参数的每个键和值:

type Value<Prop extends PropertyKey> = { oneOfKeys: Prop }

function testFunc<
  ParentKey extends PropertyKey,
  Item extends Value<ParentKey>,
  Config extends Record<ParentKey, Item>
>(foo: Config) {}

ParentKey 推断所有父键。

Item 推断所有嵌套对象:{oneOfKeys: string}

Config 推断整个配置。

您可以在我的article 中找到有关推断参数的更多信息

然后,您需要遍历推断的 Config 并找到不允许的值:

type Validation<Config extends Record<PropertyKey, Value<PropertyKey>>> = {
  [Prop in keyof Config]: Config[Prop]['oneOfKeys'] extends keyof Config ? Config[Prop] : Value<never>
}

type Test = Validation<{
  foo: {
    oneOfKeys: "bar"
  },
  bar: {
    oneOfKeys: "baz"
  },
  bal: { //TS is complaining about the "bal" key
    oneOfKeys: "foo"
  }
}>

Validation - 遍历每个 Config 属性并检查 Config[Prop]['oneOfKeys'] 是否扩展了任何父键。如果是 - 保持原样,否则将 value 替换为 never。

全码:


type Validation<Config extends Record<PropertyKey, Value<PropertyKey>>> = {
  [Prop in keyof Config]: Config[Prop]['oneOfKeys'] extends keyof Config ? Config[Prop] : Value<never>
}

type Test = Validation<{
  foo: {
    oneOfKeys: "bar"
  },
  bar: {
    oneOfKeys: "baz"
  },
  bal: { 
    oneOfKeys: "foo"
  }
}>

type Value<Prop extends PropertyKey> = { oneOfKeys: Prop }

function testFunc<
  ParentKey extends PropertyKey,
  Item extends Value<ParentKey>,
  Config extends Record<ParentKey, Item>
>(foo: Validation<Config>) {}

testFunc({
  foo: {
    oneOfKeys: "bar"
  },
  bar: {
    oneOfKeys: "baz" // error
  },
  bal: { 
    oneOfKeys: "foo"
  }
})

Playground

【讨论】:

  • 感谢您的帮助,但您的代码似乎不起作用,我没有收到任何错误。
  • @VincentJ 我的错。我忘了把Config 换成Validator。它现在应该可以工作了
  • 谢谢。我有点失望,没有更简单的方法可以做到这一点。它还会为最终用户生成相当神秘的错误消息。
  • 您可以使用任何消息代替 NEVER。相信我,这不是最难的方法:D
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-08-23
  • 1970-01-01
  • 2011-08-15
  • 2014-06-27
相关资源
最近更新 更多