具有定义值的打字稿动态对象键
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,我们在这里为您提供帮助。您能否发布一个完整的示例,确切地说明您想要发生的事情?
好的,我已经更新了问题,以包含一个更清晰的示例来说明我想要实现的目标。让我知道我是否应该为此创建某种指向游乐场的链接。
所以您想“扩展”GameObj
和 getSkillLevel
以继续接受正确的键,而不仅仅是字符串,对吧?
【参考方案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<typeof GAMES_OBJECT>
类型的参数,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
?以上是关于具有定义值的打字稿动态对象键的主要内容,如果未能解决你的问题,请参考以下文章