如何确保嵌套对象中的字符串引用对象键之一

2023-12-04

如何使打字稿将字符串类型限制为仅是同一对象中的键?下面是一个简化的例子..我尝试了多种K extends string输入逻辑但我被难住了。

{
  players: {
    uniquePlayerId1: {
       isTargeting: 'uniquePlayerId2' // should only be able to be `uniquePlayerId1` or `uniquePLayerId2` or any other keys of the `players` object
    },
    uniquePlayerId2: {
      isTargeting: 'uniquePlayerId1', // should only be able to be `uniquePlayerId1` or `uniquePLayerId2` or any other keys of the `players` object
    },
  }
}

我的用例是我正在构建一个游戏 - 该示例已简化,但本质上每个玩家都可以瞄准另一个玩家 - 由 的值表示isTargeting- 要么是一个字符串,我想强制它成为对象的键之一,例如uniqueId1 or uniqueId2在示例中,或 null - 他们不针对任何人。


没有specific输入以这种方式工作的 TypeScript;如果你静态地知道键列表K in players, (say "uniquePlayerId1" | "uniquePlayerId2")对于任何特定的键选择players,那么你可以定义一个特定的类型PlayersType<K>就它而言就像

type PlayersType<K extends string> =
  { players: Record<K, { isTargeting: K }> }

但既然你不知道K提前,你想定义SomePayersType这等于PlayersType<K> for some K。这种类型称为存在量化泛型并且,尽管已被要求(参见微软/TypeScript#14466),TypeScript 不直接支持此类类型。与大多数具有泛型的语言一样,TypeScript 仅具有普遍量化通用类型。如果我们有存在量化的泛型,你可能会说:

// not valid TypeScript, don't try this:
type SomePlayersType = <exists K extends string> PlayersType<K>

但现在你还不能。有多种方法可以用通用类型来模拟存在类型,但它们很麻烦,并且不一定适合您的用例。


相反,你可以做的是采取PlayersType<K>并创建一个通用辅助身份函数来推断K从传入的值中为您提供。它可能看起来像这样:

const asPlayers = <K extends string>(
  obj: PlayersType<K>
) => obj;

但不幸的是,这并不完全按照你想要的方式工作;编译器实际上推断K来自isTargeting值而不是来自键players,这意味着它将拒绝有效的参数:

const players = asPlayers({
  players: {
    uniquePlayerId1: {
      isTargeting: 'uniquePlayerId2' // error?!
    },
    uniquePlayerId2: {
      isTargeting: 'uniquePlayerId2'
    },
  }
})

我们想告诉编译器不要推断K from isTargeting;也就是说,使isTargeting a 非推理类型参数用法 of K,按照要求微软/TypeScript#14829。有不同的方法可以实现这种行为;在这种情况下,我们只需添加一个新的类型参数L这是受约束的 to K,并使用K对于按键和L for isTargeting:

const asPlayers = <K extends string, L extends K>(
  obj: { players: Record<K, { isTargeting: L }> }
): PlayersType<K> => obj;

现在我们来使用它:

const players = asPlayers({
  players: {
    uniquePlayerId1: {
      isTargeting: 'uniquePlayerId2'
    },
    uniquePlayerId2: {
      isTargeting: 'uniquePlayerId1'
    },
  }
})
/* const players: PlayersType<"uniquePlayerId1" | "uniquePlayerId2"> */

看起来不错;编译器推断players属于类型PlayersTaype<"uniquePlayerId1" | "uniquePlayerId2">。让我们看看如果我们传入一些不好的东西会发生什么:

const badPlayers = asPlayers({
  players: {
    uniquePlayerId1: {
      isTargeting: "oopsie" // error
      // Type '"oopsie"' is not assignable to 
      // type '"uniquePlayerId1" | "uniquePlayerId2"'
    },
    uniquePlayerId2: {
      isTargeting: 'uniquePlayerId1'
    }
  }
})

在这里我们看到了所需的编译器错误;因为没有名为的键"oopsie", the isTargeting属性无法命名"oopsie".

所以就这样吧。而不是有具体的SomePlayersType类型,你有一个PlayersType<K>输入您推断的位置K从价值。这是存在主义类型的“半途”,可能足以满足您的需求。您可能会发现自己携带了这个额外的类型参数,所以也许您想使用更简单的东西,例如PlayersType<string>您控制的内部代码,并且仅强制执行以下推理K对于不太可信的代码:

function publicFacingCode<K extends string>(players: PlayersType<K>) {
  innerLibraryCode(players);
}

function innerLibraryCode(players: PlayersType<string>) {

}

Playground 代码链接

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何确保嵌套对象中的字符串引用对象键之一 的相关文章

随机推荐