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

问题描述 投票:0回答:4

我遇到了一个问题,试图让打字稿识别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
)并且能够:

  1. 提取对象的精确键(无需键入每个键的定义)。
  2. 定义每个键的类型,而不是将键覆盖为
    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

typescript typescript-typings typescript-generics
4个回答
6
投票

虽然我还没有找到一种方法来完全避免创建 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

这样我们就得到:

  1. 缺少属性时出现编译错误。
  2. 自动补全缺失的属性。
  3. 能够提取对象中定义的确切键,而无需在其他地方定义这些键,例如在枚举中(复制它)。

我已经更新了我的要点以包含此示例,您也许可以在typescript Playground上看到它的实践。


3
投票

希望这对您现在有帮助:

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

游乐场链接


1
投票

我这样编写代码:

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;
}

0
投票

自 TypeScript 4.9 起,

satisfies
运算符可用于验证表达式是否与特定类型匹配,而无需更改该表达式的类型。

const GAMES = Object.freeze({
  CSGO: {
    skillLevel: 'good'
  },
  WOW: {
    skillLevel: 'average'
  }
}) satisfies GameInfo;
© www.soinside.com 2019 - 2024. All rights reserved.