设置 Key/Value 的函数,其中键是文字(而不仅仅是字符串)

Function which sets a Key/Value where key is a literal (rather than just a string)

我希望我的简单 KV 函数生成一个对象,其中 key 是文字类型,而不是扩展为 string:

function KV<K extends Readonly<string>, V extends any>(key: K, value: V) {
  return { [key as K]: value };
}

type Target = {foo: number};
const t: Target = KV("foo", 43);

type Target2 = { bar: (bar: string) => {bar: string}};
const t2: Target2 = KV("bar", (bar: string) => ({ bar }));

不幸的是,虽然通用 K 定义为文字,但我找不到强制 returned 键值的方法配对以将密钥识别为文字。

我觉得要让它工作我需要更明确地了解 return 类型所以我尝试了 KV 函数,如下所示:

function KV2<O extends object, K extends Readonly<string> & keyof O, V extends any, KV extends O & [K: V]>(key: K, value: V): KV {
  return { [key as K]: value } as KV;
}

我的希望很高,但不幸的是没有培根。

此后我尝试了更多的变体,但没有得到它的工作。任何更精通文字类型的黑暗艺术的人有什么想法吗?

Code can be found in this Playground

好的,我想我已经回答了我的问题,所以我会 post 在这里回答我的问题,但如果有更好的解决方案,我愿意接受,并会在关闭前再开放一天:

解法:

export function KV<
  K extends string, 
  V extends any, 
  O extends Record<K, V>
>(key: K, value: V): O {
  return { [key as K]: value } as O;
}

这样我就可以组合键值,当我将它们合并在一起时,完整的类型将被保留,因为键是文字类型。所以在这个例子中:

const kv = KV("foo", 43);
const kv2 = KV("bar", (bar: string) => ({ bar }));

const both = { ...kv, ...kv2 };

类型both保留了kvkv2的所有类型信息。


根据@jcalz 的有用反馈,我从两个方面调整了我的解决方案。首先,上面的实现已简化为:

export function KV<V extends any, K extends string>(key: K, value: V) {
  return { [key]: value } as Record<K, V>;
}

这将推断出 keyvalue 并且是一个很好的紧凑签名。但是,如果您想将 value 限制为更窄的定义,这将不起作用,因为这将需要声明两个泛型。这可以通过将构造与函数分开来避免,因此我为这个用例引入了 KV2

export const KV2 = <V extends any>() => <K extends string>(key: K, value: V) => {
  return { [key]: value } as Record<K, V>;
};

这将允许您——例如——接受键值,其中值是必须以“kv-is-great”开头的字符串:

const kv = 
KV2<`kv-is-great${string}`>()("foo", "kv-is-great-or-is-it?");

就其本身而言,这似乎不是很有用,但在更大的组合中能够约束 value 应该是一个有用的补充,但代价是稍微有点尴尬 API.