打字稿对象字面量缩小
Typescript object literal narrowing
我在谷歌上搜索了将近两个小时,所以现在我转向 Stack Overflow 寻求帮助。我正在编写一个接受对象参数的函数。我不知道如何在传递对象文字时强制 TS 缩小到类型。我的简单工作示例看起来像下面的代码,但感觉不必要地麻烦:
type ValueOf<T> = T[keyof T];
const getRandomValue = <T, S=Record<string, T>>(obj:S):T => {
const values = Object.values(obj);
const randomIndex = Math.floor(Math.random() * values.length);
return values[randomIndex];
}
const obj = {
a: "hi",
b: "hello",
} as const;
//this works as expected but it's cumbersome to define the literal separately and have to say "as const" to avoid the type becoming { string: string }
const value = getRandomValue<ValueOf<typeof obj>>(obj);
//prove that it works
type ValueType = typeof value;
const x:ValueType = "hello"; //allowed
const y:ValueType = "bonjour"; //shows a type error
//I was hoping I could define the function in a way where I could call it like this and still
//get the same strict typing as above: getRandomValue({ a: "hi", b: "hello" });
编辑:删除了代码的非工作版本。
当您使用 T = Record<string, string>
时,您的 ValueOf<T>
会发生什么情况是 TS(正确地)确定 Record<string, string>[string] = string
.
当您将 getRandomValue<T>(obj: T): ValueOf<T>
与对象文字一起使用时,例如 { foo: "bar" }
,T
不是 Record<string, string>
而是 Record<"foo", "bar">
.
因此您需要对 记录的 键和值类型使用泛型:
const getRandomValueFromObj = <Key, Value>(obj: Record<Key, Value>): Value => {}
但是,出于几个不同的原因,这还不够。首先,TS 仍会确定 Key = string
和 Value = string
,例如{ foo: "bar" }
,但你想要字符串常量。你可以写 { foo: "bar" as const }
来向 TS 保证 "bar"
是常量,但是在函数定义中使用 generic constraint 更容易:
const getRandomValueFromObj =
<Key, Value extends string>(obj: Record<Key, Value>): Value => {}
现在打字稿将为 { foo: "bar" }
确定 Value = "bar"
。
接下来,您将遇到 Key
和 Object.keys
的问题,它们的签名是 .keys(o: {}): string[]
。也就是说,即使 o
是 Record<Key, Value>
,Object.keys
也总是 return string[]
,而 that's intentional。您必须使用类型断言:
const getRandomValueFromObj =
<Key, Value extends string>(obj: Record<Key, Value>): Value => {
const keys = Object.keys(obj) as Key[];
}
但 TS 仍然不高兴,因为 Key
和 string
可能不重叠。毕竟,Key
可以是任何东西!您还必须限制 Key
:
const getRandomValueFromObj =
<K extends string, V extends string>(obj: Record<K, V>): V => {
const keys = Object.keys(obj) as Key[];
}
这是一个可以使用的游乐场 link。
我在谷歌上搜索了将近两个小时,所以现在我转向 Stack Overflow 寻求帮助。我正在编写一个接受对象参数的函数。我不知道如何在传递对象文字时强制 TS 缩小到类型。我的简单工作示例看起来像下面的代码,但感觉不必要地麻烦:
type ValueOf<T> = T[keyof T];
const getRandomValue = <T, S=Record<string, T>>(obj:S):T => {
const values = Object.values(obj);
const randomIndex = Math.floor(Math.random() * values.length);
return values[randomIndex];
}
const obj = {
a: "hi",
b: "hello",
} as const;
//this works as expected but it's cumbersome to define the literal separately and have to say "as const" to avoid the type becoming { string: string }
const value = getRandomValue<ValueOf<typeof obj>>(obj);
//prove that it works
type ValueType = typeof value;
const x:ValueType = "hello"; //allowed
const y:ValueType = "bonjour"; //shows a type error
//I was hoping I could define the function in a way where I could call it like this and still
//get the same strict typing as above: getRandomValue({ a: "hi", b: "hello" });
编辑:删除了代码的非工作版本。
当您使用 T = Record<string, string>
时,您的 ValueOf<T>
会发生什么情况是 TS(正确地)确定 Record<string, string>[string] = string
.
当您将 getRandomValue<T>(obj: T): ValueOf<T>
与对象文字一起使用时,例如 { foo: "bar" }
,T
不是 Record<string, string>
而是 Record<"foo", "bar">
.
因此您需要对 记录的 键和值类型使用泛型:
const getRandomValueFromObj = <Key, Value>(obj: Record<Key, Value>): Value => {}
但是,出于几个不同的原因,这还不够。首先,TS 仍会确定 Key = string
和 Value = string
,例如{ foo: "bar" }
,但你想要字符串常量。你可以写 { foo: "bar" as const }
来向 TS 保证 "bar"
是常量,但是在函数定义中使用 generic constraint 更容易:
const getRandomValueFromObj =
<Key, Value extends string>(obj: Record<Key, Value>): Value => {}
现在打字稿将为 { foo: "bar" }
确定 Value = "bar"
。
接下来,您将遇到 Key
和 Object.keys
的问题,它们的签名是 .keys(o: {}): string[]
。也就是说,即使 o
是 Record<Key, Value>
,Object.keys
也总是 return string[]
,而 that's intentional。您必须使用类型断言:
const getRandomValueFromObj =
<Key, Value extends string>(obj: Record<Key, Value>): Value => {
const keys = Object.keys(obj) as Key[];
}
但 TS 仍然不高兴,因为 Key
和 string
可能不重叠。毕竟,Key
可以是任何东西!您还必须限制 Key
:
const getRandomValueFromObj =
<K extends string, V extends string>(obj: Record<K, V>): V => {
const keys = Object.keys(obj) as Key[];
}
这是一个可以使用的游乐场 link。