这不是类型安全的有充分的理由。虽然T extends string 将确保T 是string 的子类型,但认为T 必须是字符串文字类型是错误的。它也可以是字符串文字类型的联合:
const singleton = <K extends string, V>(k: K, v: V) => ({[k]: v})
const key = Math.random() > 0.5 ? "baz": "bar";
const foo: { bar: number, baz: number } = singleton(key, 0) as Record<typeof key, number>;
// one of these, at random will fail at runtime
foo.bar.toExponential
foo.baz.toExponential
Playground Link
现在打字稿可以在这里做得更好吗?可以说它可以推断出Partial<Record<K, V>>。还有一个关于这个主题的问题,我目前找不到。
要获得你想要的类型,你需要在函数体中使用类型断言。您可以向Record<K, V> 断言(需要注意的是,如果K 是一个联合,这是不安全的)或者您可以向Partial<Record<K, V>> 断言,这样更安全但不太方便。
const singleton = <K extends string, V>(k: K, v: V) => ({[k]: v})as Partial<Record<K, V>>;
const key = Math.random() > 0.5 ? "baz": "bar";
const foo = singleton(key, 0)
foo.bar?.toExponential
foo.baz?.toExponential
Playground Link
如果你想发挥创意,你可以测试一下T 是否是一个联合体。使用类似this
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never
const singleton = <K extends string, V>(k: K, v: V) => ({[k]: v})as ( [K] extends [UnionToIntersection<K>] ? Record<K, V>: Partial<Record<K, V>>);
const key = Math.random() > 0.5 ? "baz": "bar";
const foo = singleton(key, 0)
foo.bar?.toExponential
foo.baz?.toExponential
const foo2 = singleton("key", 0)
foo2.key
Playground Link