首页 > 解决方案 > 如何定义对象的类型以仅接受来自枚举的键?

问题描述

我有一个枚举:

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
}

标签: reactjstypescript

解决方案


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

  • 既然vSomeInterface,看来someValue一定是一个数组SomeInterface

  • 我猜想res在回调函数的主体中reduce是所需的返回值myMethod,因为你在回调函数中设置了它的值。myMethod如果我们用名字来调用返回值的类型RetType,那就意味着RetType必须是的键keyof typeof MyEnum

  • 您正在使用v.nameas 那些键,因为vis SomeInterface,那么我猜SomeInterface's 的name属性也应该是keyof typeof MyEnum.

  • 您正在考虑pushvres,因此您希望 的属性res是 的数组SomeInterface。因此,RetType类似于Record<keyof typeof MyEnum, SomeInterface[]>,其中Record<K,V>是标准库中的一种类型,表示具有 in 中的键K和值的对象V

  • 但是,当然,由于someValue可能是一个空数组,或者没有与 的键一样多的元素MyEnum,那么RetType可能会丢失一些属性。所以它更像是标准库Partial<Record<keyof typeof MyEnum, SomeInterface[]>>Partial<T>的一个类型,它使属性成为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. 这允许编译器的控制流分析工作,并确保它resVName实际上是 aSomeInterface[]而不是 a SomeInterface[] | undefinedres[v.name]由于 TypeScript 中的错误/限制,控制流分析不适用于括号属性访问表示法,因此它不起作用。这有点烦人,但确保类型安全可能是值得的。

该代码的类型都很好,并且可能在运行时工作(您需要检查)。但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
}

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


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


推荐阅读