【问题标题】:How to define type of an object to accept only keys from enum?如何定义对象的类型以仅接受来自枚举的键?
【发布时间】:2018-07-13 19:59:20
【问题描述】:

我有一个枚举:

enum MyEnum {
    One,
    Two,
    Three
}

然后我有一个 React Component 的方法,它应该返回一个对象,其键只能是 MyEnum 的字符串键(意思是到目前为止:一、二、三):

myFunction = (someValue: {}[]): {} => {
    return someValue.reduce((res, v, i) => {
         res[v.name].push(v)
         return res
    }, {})
}

问题:如何动态定义该函数的返回值类型为对象,其键只能是MyEnum的字符串键?

现在只有 3 个,但是如果我在枚举中添加另一个,myFunction 的返回值的类型也应该更新。

更新 1:

尝试将Record<keyof typeof MyEnum, any> 分配为返回值:

myMethod = (someValue: {}[]): Record<keyof typeof MyEnum, any> => {
    return someValue.reduce((res: any, v: SomeInterface) => {
         res[v.name].push(v)
         return res
    }, {})
}

这不会抛出任何错误,但是如果我将reduce的返回值分配给一个变量,然后在其上添加另一个属性(其键不是MyEnum键),就不会出现错误,这不是我所寻求的:

myMethod = (someValue: {}[]): Record<keyof typeof MyEnum, any> => {
    let someVar = someValue.reduce((res: any, v: SomeInterface) => {
         res[v.name].push(v)
         return res
    }, {})
    someVar.keyName = 'value'
    return someVar
}

【问题讨论】:

  • Record&lt;keyof typeof MyEnum, any&gt;?
  • @jcalz 什么是“记录”?
  • standard library 中的类型,其中Record&lt;K,V&gt; 表示“具有键K 和值V 的对象”。
  • @jcalz 我有一个 .reduce 方法,它产生返回值。并且整个构造都带有一个错误,即 MyEnum 的字符串值之一在函数的返回值中丢失。
  • 您能否将相关代码和错误连同编译器错误一起编辑到您的问题中,以便有人可以帮助您调试?

标签: reactjs typescript


【解决方案1】:

所以,看起来这里发生了很多事情。我的观察和猜测:

  • 由于vSomeInterface,看来someValue一定是SomeInterface的数组。

  • 我猜想reduce 的回调函数主体中的resmyMethod 的期望返回值,因为您在回调函数中设置了它的值。如果我们将myMethod的返回值类型命名为RetType,则意味着RetType的键必须是keyof typeof MyEnum

  • 你使用v.name作为那些键,因为vSomeInterface,那么我猜SomeInterfacename属性也应该是keyof typeof MyEnum

  • 你是 pushing vres 的值,所以你期望 res 的属性是 SomeInterface 的数组。因此,RetType 类似于Record&lt;keyof typeof MyEnum, SomeInterface[]&gt;,其中Record&lt;K,V&gt; 是标准库中的一个类型,表示具有K 中的键和V 中的值的对象。

  • 当然,因为someValue 可能是一个空数组,或者没有MyEnum 的键那么多的元素,那么RetType 可能会丢失一些属性。所以它更像Partial&lt;Record&lt;keyof typeof MyEnum, SomeInterface[]&gt;&gt;,其中Partial&lt;T&gt; 是标准库中的一个类型,它使得T 中的属性是可选的。

  • 现在,我不确定您是否完全理解 reduce 的工作方式,但回调需要返回与预期返回值相同类型的内容。第二个参数是“初始值”,它也必须是与预期返回值相同的类型。因此,我必须更改您的回调,使其返回res,并更改初始值,使其也为RetType

  • 我还注意到,由于初始值是一个空对象,所以当你第一次尝试将push 某个东西放到它的一个数组属性上时,你会发现它是undefined。所以你需要检查它并将其设置为一个空数组,然后再尝试push

这让我想到了这样的事情:

enum MyEnum {
  One,
  Two,
  Three
}

interface SomeInterface {
  name: keyof typeof MyEnum
};

type RetType = Partial<Record<keyof typeof MyEnum, SomeInterface[]>>

const myMethod = (someValue: SomeInterface[]): RetType => {
  let someVar = someValue.reduce((res: RetType, v: SomeInterface) => {
    const resVName = res[v.name] || []; // make sure it's set
    resVName.push(v);
    res[v.name]=resVName; // copy back in case it's a new array
    return res;
  }, {} as RetType)
  //someVar.keyName = 'value' // errors now
  return someVar
}

请注意,我在一个名为 resVName 的新值之间进行了复制。这允许编译器的control flow analysis 工作并确保resVName 实际上是SomeInterface[] 而不是SomeInterface[] | undefined。它不适用于 res[v.name],因为 TypeScript 中的 bug/limitation 控制流分析不适用于括号属性访问表示法。这有点烦人,但确保类型安全可能是值得的。

该代码的类型都很好,并且可能在运行时工作(您需要检查)。但是reduce 看起来不像你想要做的。相反,我会说您正在寻找更简单的 forEach 方法,如下所示:

const myMethod = (someValue: SomeInterface[]): RetType => {
  let someVar: RetType = {};
  someValue.forEach((v: SomeInterface) => {
    const someVarVName = someVar[v.name] || []; // make sure it's set
    someVarVName.push(v);
    someVar[v.name] = someVarVName; // copy back in case it's a new array
  });
  //someVar.keyName = 'value'
  return someVar
}

这也有效,并且不会在累加器周围乱转。无论哪种方式都很好,可能。


好的,希望这有点道理,并给你一些想法。祝你好运!

【讨论】:

  • 惊人的答案!根据您的要求详细说明最初的问题是绝对值得的。非常感谢!我错过了您的第 2 点和第 4 点以及部分第 5 点:第 5 点的其余部分以及整个第 6 点在问题中被无意中省略了。
  • 但是reduce有什么问题?
  • 好吧,reduce 是一个 fold 操作,它从中间返回值构建最终返回值。但在你的情况下,返回值总是相同的,你只是在改变其中的数据。所以reduce是不必要的,你还不如使用forEach,它不关心返回值。
猜你喜欢
  • 1970-01-01
  • 2019-07-21
  • 2019-04-03
  • 1970-01-01
  • 2021-07-08
  • 2022-01-19
  • 1970-01-01
  • 2011-09-11
  • 1970-01-01
相关资源
最近更新 更多