输入 TypeScript 泛型的重复模式
我想为一个通用函数接受无限参数,该函数将有效键与.连接在一起。理论上,这可以接受的参数数量与嵌套对象的数量一样多。
以下代码片段可以正常工作,但我想知道它是否可以接受任意数量的参数,而不仅仅是我手动输入的那些?
const tt = <Lang>() => {
return <
A extends keyof Lang,
B extends keyof Omit<Lang[A], keyof string>,
C extends keyof Omit<Lang[A][B], keyof string>,
D extends keyof Omit<Lang[A][B][C], keyof string>,
E extends keyof Omit<Lang[A][B][C][D], keyof string>,
F extends keyof Omit<Lang[A][B][C][D][E], keyof string>
// Can this repeat without manually typing each out?
>(
a: A,
b?: B,
c?: C,
d?: D,
e?: E,
f?: F,
) => {
return [a, b, c, d, e, f].filter(Boolean).join('.');
};
};
console.log(tt<{Parent: {child: 'child'}}>()('Parent', 'child'));
// Console: "Parent.child"
代码可以按原样工作,但它应该能够接受无限数量的参数。
也许像这样:
const tt = <Lang>() => {
return <A extends SomethingGoesHere>(...args: A) => {
return args.join('.');
};
};
console.log(tt<{Parent: {child: 'child'}}>()('Parent', 'child'));
// Console: "Parent.child"
一个后续问题:我讨厌为了让泛型类型工作,这必须是一个高阶函数。为什么 TypeScript 不允许我根据提供的泛型类型 Lang 推断 A-F?
在理想的世界里,我本可以做到这一点:
const tt = <
Lang,
A extends keyof Lang,
B extends keyof Omit<Lang[A], keyof string>,
C extends keyof Omit<Lang[A][B], keyof string>,
D extends keyof Omit<Lang[A][B][C], keyof string>,
E extends keyof Omit<Lang[A][B][C][D], keyof string>,
F extends keyof Omit<Lang[A][B][C][D][E], keyof string>
>(
a: A,
b?: B,
c?: C,
d?: D,
e?: E,
f?: F,
) => {
return [a, b, c, d, e, f].filter(Boolean).join('.');
};
tt<{Parent: {child: 'child'}}>('Parent', 'child');
// ^ error: Expected 7 type arguments, but got 1.ts(2558)
但是它抛出了那个错误。
答案#1
@TitianCernicova-Dragomir 是对的,这里实际上没有更好的办法(至少没有受支持的方法)。有一件事通常比与 TypeScript 的类型系统斗争更有效,那就是将代码重构成它更容易理解的形式。
例如,如果不是支持可变参数函数,而是创建一个可链式调用的函数,每次调用都会将当前类型 T 缩小为它的某个属性 T[K],会怎么样?类似这样的:
type ObjStrKey<T> = T extends object ? Extract<keyof T, string> : never;
interface Keychain<T> {
<K extends ObjStrKey<T>>(k: K): Keychain<T[K]>;
value: string;
}
function keychain<T>(): Keychain<T> {
function kc<T, K extends ObjStrKey<T>>(
this: Keychain<T>,
k: K
): Keychain<T[K]> {
const value = (this.value ? this.value + "." : "") + k;
const v = { value };
return Object.assign(kc.bind(v), v);
}
const v = { value: "" };
return Object.assign(kc.bind(v), v);
}
其工作原理如下:
interface Person {
parent: Person;
child: Person;
spouse: Person;
name: string;
}
const kcp = keychain<Person>();
console.log(kcp("child")("spouse")("child")("parent")("name").value);
// child.spouse.child.parent.name
代码链接
这对编译器来说很容易,因为每个函数调用只执行一次属性查找。
如果你觉得弄 this 和 bind 有点奇怪,你可以将其改为常规的 class 并将函数调用改为方法调用,像这样:
type ObjStrKey<T> = T extends object ? Extract<keyof T, string> : never;
class Keychain<T> {
static make<T>() {
return new Keychain<T>("");
}
private constructor(public value: string) {}
k<K extends ObjStrKey<T>>(k: K): Keychain<T[K]> {
const value = (this.value ? this.value + "." : "") + k;
return new Keychain<T[K]>(value);
}
}
这有点不同:
const kcp = Keychain.make<Person>();
console.log(kcp.k("child").k("spouse").k("child").k("parent").k("name").value);
// child.spouse.child.parent.name
代码链接
无论如何,这只是一个想法。希望有帮助,祝你好运!
