您可以采取的一种方法是将函数generic设置为类型N
的nameField
和类型I
的idField
,然后使option
的类型已知在键N
处具有string
属性,在键I
处具有number
属性.这意味着调用签名就像
const foo = <N extends PropertyKey, I extends PropertyKey>(
option: Record<N, string> & Record<I, number>,
nameField: N,
idField: I
) => {};
Record<K, V>
utility type指的是在类型K
的键处具有类型V
的属性的对象,而intersection组合了这两种类型.因此Record<N, string> & Record<I, number>
意味着"在关键字N
处具有string
属性并且在关键字I
处具有number
属性的类型".
好的,但是如果调用方没有提供参数,您希望nameField
和idField
分别为optional,缺省值分别为"name"
和"id"
.这不适合泛型,因为在这种情况下,无法知道N
或I
(由调用者 Select )实际上是"name"
还是"id"
(由实现 Select ).请参见How to give a default value to a generic parameter?和microsoft/TypeScript#49158.在这两个地方提到的一个解决办法是,如果没有提供nameField
和idField
的参数,则给出N
和I
default type arguments,然后只给assert "name"
和"id"
是N
和I
类型的.这看起来像是:
const foo = <
N extends PropertyKey = "name",
I extends PropertyKey = "id"
>(
option: Record<N, string> & Record<I, number>,
nameField = 'name' as N,
idField = 'id' as I
) => {};
这接近于我们想要的结果,除了一些反复无常的泛型类型推断将导致从option
推断出N
和I
,如果nameField
和idField
被省略.但我们真的不想要这种行为;N
和I
应该从nameField
和idField
推断,或者说默认为"name"
和"id"
……而option
应该只是checked.这意味着Record<N, string> & Record<I, number>
内的N
和I
应该是microsoft/TypeScript#14829中所要求的non-inferential type parameter usages.这里没有本机NoInfer
可用,所以我们不能在自己不实现NoInfer
的情况下编写Record<NoInfer<N>, string> & Record<NoInfer<I>, number>
.GitHub问题中提到的一种技术是与空对象类型(如type NoInfer<T> = T & {}
)相交,这会降低推理优先级.让我们在这里这样做:
const foo = <
N extends PropertyKey = 'name',
I extends PropertyKey = 'id'
>(
option: Record<N & {}, string> & Record<I & {}, number>,
nameField = 'name' as N,
idField = 'id' as I,
) => {
const name: string = option[nameField];
const id: number = option[idField];
const result = { id, name };
console.log(id.toFixed())
};
这是我们的最终形式.
让我们来测试一下:
const option = {
id: '2',
name: 'Foo',
value: 2
};
const a = foo(option); // Error here
const b = foo(option, 'name', 'value'); // okay
const c = foo({ name: "abc", id: 123 }); // okay
const d = foo({ name: "abc", id: "123" }); // error
一切都如你所愿.请注意,泛型缺省值的解决方法确实存在潜在的失败模式,在这种模式下,调用方手动指定N
和I
类型参数,而不传入nameField
和idField
参数:
foo<"name", "value">(option); // no compiler error!
这不太可能,但你应该意识到这一点.
Playground link to code个