具有定义值的打字稿动态对象键

Posted

技术标签:

【中文标题】具有定义值的打字稿动态对象键【英文标题】:Typescript dynamic object keys with defined values 【发布时间】:2020-07-15 17:23:13 【问题描述】:

我遇到了一个问题,试图让 typescript 为我识别 javascript 对象的键,同时强制每个键的值类型,因为我想创建对象的键的类型,所以我不能只创建一个常规type MyObject = [key: string]: <insert type>

想象一个对象myobject,我在其中提取它的键:

const myobject = 
  foo: ,
  bar: 
;

type MyObjectKeys = keyof typeof myobject; // 'foo' | 'bar'

如何将类型定义添加到键的值中,同时仍然能够提取/继承键的定义?如果我这样做,那么我将不再能够提取对象的确切键,而只能提取类型(字符串):

type MyObject =  [key: string]:  value: boolean 
const myobject = 
  foo:  value: true ,
  bar:  value: false 
;

type MyObjectKeys = keyof typeof myobject; // string

我想我可以通过创建一个辅助函数来实现这一点:

function enforceObjectType<T extends MyObject>(o: T) 
    return Object.freeze(o);

const myobject = enforceObjectType(
  foo: ,
  bar: 
);

但我更愿意为它定义一个明确的类型,而不必污染代码,只写与类型相关的函数。有没有办法允许一组字符串作为一个类型的键而不重复?

这样做的目的是让 TypeScript 帮助指出正确的对象键,例如(实际用法有点复杂,所以我希望这描述得足够好):

type MyObjectKeys = keyof typeof myobject; // string
function getMyObjectValue(key: MyObjectKeys) 
   const objectValue = myobject[key];


// suggest all available keys, while showing an error for unknown keys
getMyObjectValue('foo'); // success
getMyObjectValue('bar'); // success 
getMyObjectValue('unknown'); // failure

总结:我想将一个对象定义为 const(实际上是 Object.freeze)并且能够:

    提取对象的准确键(无需键入每个键的定义)。 定义每个键的类型,而不是覆盖string 的键,而不是它们的本质 - 例如'foo' | 'bar'

完整示例

type GameObj =  skillLevel: EnumOfSkillLevels ; // ADD to each key.
const GAMES_OBJECT = Object.freeze(
   wow:  skillLevel: 'average' ,
   csgo:  skillLevel 'good' 
);

type GamesObjectKeys = keyof typeof GAMES_OBJECT;

function getSkillLevel(key: GamesObjectKeys) 
  return GAMES_OBJECT[key]


getSkillLevel('wow') // Get the actual wow object
getSkillLevel('unknown') // Get an error because the object does not contain this.

根据上述,我不能执行以下操作,因为这会覆盖任何字符串的已知键:

type GameObj =  [key: string]: skillLevel: EnumOfSkillLevels ;
const GAMES_OBJECT: GameObj = Object.freeze(
   wow:  skillLevel: 'average' ,
   csgo:  skillLevel 'good' 
);

type GamesObjectKeys = keyof typeof GAMES_OBJECT;

function getSkillLevel(key: GamesObjectKeys) 
  return GAMES_OBJECT[key]


getSkillLevel('wow') // Does return wow object, but gives me no real-time TS help
getSkillLevel('unknown') // Does not give me a TS error

另一个例子:例如看this gist,如果你想修改代码,复制到typescript playground

【问题讨论】:

我不是很明白...你想要一个接收任何给定对象的键作为参数的函数吗?还是要将myObject 设置为常量? 对此我很抱歉。我会尝试更好地编辑和表达它,但我基本上只是想定义myobject的每个键的类型,同时仍然能够动态“继承”这些键。 Np,我们在这里为您提供帮助。您能否发布一个完整的示例,确切地说明您想要发生的事情? 好的,我已经更新了问题,以包含一个更清晰的示例来说明我想要实现的目标。让我知道我是否应该为此创建某种指向游乐场的链接。 所以您想“扩展”GameObjgetSkillLevel 以继续接受正确的键,而不仅仅是字符串,对吧? 【参考方案1】:

虽然我还没有找到一种方法来完全避免创建一个 javascript 函数来解决这个问题(也被告知这可能根本不可能,目前),但我找到了我认为可以接受的解决方案:

type GameInfo =  [key: string]:  skillLevel: 'good' | 'average' | 'bad' 

type EnforceObjectType<T> = <V extends T>(v: V) => V;
const enforceObjectType: EnforceObjectType<GameInfo> = v => v;

const GAMES2 = enforceObjectType(
  CSGO: 
    skillLevel: 'good',
  ,
  WOW: 
    skillLevel: 'average'
  ,
  DOTA: 
    // Compile error - missing property skillLevel
  
);

type GameKey2 = keyof typeof GAMES2;

function getGameInfo2(key: GameKey2) 
  return GAMES2[key];


getGameInfo2('WOW');
getGameInfo2('CSGO');
getGameInfo2('unknown') // COMPILE ERROR HERE

这样我们得到:

    在缺少属性时编译错误。 自动完成缺失的属性。 能够提取对象中定义的确切键,而无需在其他地方定义键,例如在一个枚举中(复制它)。

我已更新 my Gist 以包含此示例,您可能可以在 typescript playground 上实际看到它。

【讨论】:

【参考方案2】:

希望现在对您有所帮助:

enum EnumOfSkillLevels 
  Average = 'average',
  Good = 'good'


type GameObject<T extends  [key: string]: skillLevel: EnumOfSkillLevels > = 
  [key in keyof T ]: skillLevel: EnumOfSkillLevels

const GAMES_OBJECT = 
   wow:  skillLevel: EnumOfSkillLevels.Average ,
  csgo:  skillLevel: EnumOfSkillLevels.Good ,
   lol:  skillLevel: EnumOfSkillLevels.Good 
 as const;


function getSkillLevel(key: keyof GameObject<typeof GAMES_OBJECT>) 
  return GAMES_OBJECT[key]


getSkillLevel('wow') // Does return wow object, but gives me no real-time TS help
getSkillLevel('lol') // Does return wow object, but gives me no real-time TS help
getSkillLevel('unknown') // Does give me a TS error

游乐场link.

【讨论】:

感谢您的回复。虽然我完全了​​解此解决方案,但它不是我所寻求的,因为我必须创建每个键的副本。如果您有很长的键列表,那将是多余且烦人的。 GAMES_OBJECT 是您手动更改的固定常量,还是创建另一个的基础对象? 这适合你吗? typescriptlang.org/play/#code/… 不,我的意思是我在上面评论的新链接。我删除了游戏枚举 好的,我明白了,但是getSkillLevel 接收GameObject&lt;typeof GAMES_OBJECT&gt; 类型的参数,GameObject 类型需要一个泛型来扩展您想要的类型extends [key: string]: skillLevel: EnumOfSkillLevels。所以 TS 会抱怨函数,而不是对象本身。你有问题吗?【参考方案3】:

我是这样写代码的:

function define<T>(o:T)
  return o;

type Node = 
  value: number,
  key: string

const NODE_DIC = 
  LEFT: define<Node>(
    value: 1,
    key: '1'
  ),
  RIGHT: define<Node>(
    value: 2,
    // compile error for missing `key`
  )

这是对象的类型:


    LEFT: Node;
    RIGHT: Node;

【讨论】:

为什么不直接:LEFT: ... as Node

以上是关于具有定义值的打字稿动态对象键的主要内容,如果未能解决你的问题,请参考以下文章

打字稿:创建具有相同键但不同值的对象

打字稿 |从对象 T 中提取具有类型 K 值的所有键名

如何将对象数组转换为在打字稿中具有动态键的单个对象

打字稿中具有通用键的对象

具有未知键的对象的打字稿类型,但只有数值?

如何使用带有动态对象键的打字稿