如何确保嵌套对象中的字符串引用对象键之一
How do I ensure strings within a nested object refer to one of the objects keys
如何使打字稿将字符串类型限制为仅作为同一对象中的键?下面是一个简化的示例。我尝试了各种 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
或 uniqueId2
,或者 null - 他们没有针对任何人。
TypeScript 中没有特定 类型以这种方式工作;如果您静态地知道 players
中的键列表 K
,(例如 "uniquePlayerId1" | "uniquePlayerId2"
)对于 players
中任何特定的键选择,那么您可以定义特定类型 PlayersType<K>
就好像
type PlayersType<K extends string> =
{ players: Record<K, { isTargeting: K }> }
但是因为你事先不知道K
,你想定义SomePayersType
等于PlayersType<K>
对于some K
。这种类型称为 existentially quantified generic type and, although it's been requested (see microsoft/TypeScript#14466),TypeScript 不直接支持此类类型。与大多数具有泛型的语言一样,TypeScript 只有 通用量化的 泛型类型。如果我们有存在量化的泛型,你可能会说:
// not valid TypeScript, don't try this:
type SomePlayersType = <exists K extends string> PlayersType<K>
但是现在你不能。有一些方法可以用通用类型模拟存在类型,但它们很麻烦,而且不一定适合您的用例。
相反,您可以做的是采用 PlayersType<K>
并创建一个通用的辅助身份函数,该函数从 passed-in 值中为您推断出 K
。它可能看起来像这样:
const asPlayers = <K extends string>(
obj: PlayersType<K>
) => obj;
但不幸的是,这并不完全符合您的要求;编译器实际上从 isTargeting
值而不是 players
的键推断出 K
,这意味着它将拒绝有效参数:
const players = asPlayers({
players: {
uniquePlayerId1: {
isTargeting: 'uniquePlayerId2' // error?!
},
uniquePlayerId2: {
isTargeting: 'uniquePlayerId2'
},
}
})
我们想告诉编译器不要从 isTargeting
推断出 K
;也就是说,按照 microsoft/TypeScript#14829. There are different ways to get such behavior; in this case we can just add a new type parameter L
which is constrained 到 K
中的要求,使 isTargeting
成为 K
的 non-inferential 类型参数用法 ,并使用 K
键和 L
键 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"
的密钥,因此 isTargeting
属性 无法命名为 "oopsie"
.
好了。您没有使用特定的 SomePlayersType
类型,而是使用 PlayersType<K>
类型从值中推断 K
。这是一种存在类型的“half-way”,可能足以满足您的需求。你可能会发现自己携带了这个额外的类型参数,所以你可能想在你控制的代码中使用更简单的东西,比如 PlayersType<string>
,并且只对不太受信任的代码强制执行 K
的推断:
function publicFacingCode<K extends string>(players: PlayersType<K>) {
innerLibraryCode(players);
}
function innerLibraryCode(players: PlayersType<string>) {
}
如何使打字稿将字符串类型限制为仅作为同一对象中的键?下面是一个简化的示例。我尝试了各种 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
或 uniqueId2
,或者 null - 他们没有针对任何人。
TypeScript 中没有特定 类型以这种方式工作;如果您静态地知道 players
中的键列表 K
,(例如 "uniquePlayerId1" | "uniquePlayerId2"
)对于 players
中任何特定的键选择,那么您可以定义特定类型 PlayersType<K>
就好像
type PlayersType<K extends string> =
{ players: Record<K, { isTargeting: K }> }
但是因为你事先不知道K
,你想定义SomePayersType
等于PlayersType<K>
对于some K
。这种类型称为 existentially quantified generic type and, although it's been requested (see microsoft/TypeScript#14466),TypeScript 不直接支持此类类型。与大多数具有泛型的语言一样,TypeScript 只有 通用量化的 泛型类型。如果我们有存在量化的泛型,你可能会说:
// not valid TypeScript, don't try this:
type SomePlayersType = <exists K extends string> PlayersType<K>
但是现在你不能。有一些方法可以用通用类型模拟存在类型,但它们很麻烦,而且不一定适合您的用例。
相反,您可以做的是采用 PlayersType<K>
并创建一个通用的辅助身份函数,该函数从 passed-in 值中为您推断出 K
。它可能看起来像这样:
const asPlayers = <K extends string>(
obj: PlayersType<K>
) => obj;
但不幸的是,这并不完全符合您的要求;编译器实际上从 isTargeting
值而不是 players
的键推断出 K
,这意味着它将拒绝有效参数:
const players = asPlayers({
players: {
uniquePlayerId1: {
isTargeting: 'uniquePlayerId2' // error?!
},
uniquePlayerId2: {
isTargeting: 'uniquePlayerId2'
},
}
})
我们想告诉编译器不要从 isTargeting
推断出 K
;也就是说,按照 microsoft/TypeScript#14829. There are different ways to get such behavior; in this case we can just add a new type parameter L
which is constrained 到 K
中的要求,使 isTargeting
成为 K
的 non-inferential 类型参数用法 ,并使用 K
键和 L
键 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"
的密钥,因此 isTargeting
属性 无法命名为 "oopsie"
.
好了。您没有使用特定的 SomePlayersType
类型,而是使用 PlayersType<K>
类型从值中推断 K
。这是一种存在类型的“half-way”,可能足以满足您的需求。你可能会发现自己携带了这个额外的类型参数,所以你可能想在你控制的代码中使用更简单的东西,比如 PlayersType<string>
,并且只对不太受信任的代码强制执行 K
的推断:
function publicFacingCode<K extends string>(players: PlayersType<K>) {
innerLibraryCode(players);
}
function innerLibraryCode(players: PlayersType<string>) {
}