所以我想你想要wantedSubset
符合每个属性要么是的类型true
或者它本身就是该类型的对象。我们需要做一些工作才能让 TypeScript 推断出值的类型true
被视为类型true
并不是boolean
(至少在 TS3.4 发布之前给我们带来const语境 https://github.com/Microsoft/TypeScript/pull/29510):
type WantedSubset = { [k: string]: true | WantedSubset };
const asWantedSubset = <RP extends WantedSubset>(wantedSubset: RP) => wantedSubset;
const wantedSubset = asWantedSubset({
user: {
name: true,
profile: {
isCool: true
}
}
}); // no error, so that type works.
现在来了类型RecursivePick
和朋友。首先,给定一个类型T
, 你想要一个WantedSubset
符合它的类型。我称之为RecusivePicker<T>
:
type RecursivePicker<T> = {
[K in keyof T]?: T[K] extends object | undefined ? RecursivePicker<T[K]> : true
}
So if T
is Schema
, then RecursivePicker<Schema>
给你:
type RPSchema = RecursivePicker<Schema>
// type RPSchema = {
// user?: {
// age?: true;
// name?: true;
// profile?: {
// isCool?: true;
// }
// }
// }
这可能是对类型的限制wantedSubset
你可以选择Schema
types.
这是RecursivePick
在所有递归映射条件 horror 的荣耀中:
type RecursivePick<T, KO extends RecursivePicker<T>> =
Pick<{ [K in keyof T]: K extends keyof KO ?
KO[K] extends true ? T[K] :
KO[K] extends infer KOK ?
KOK extends RecursivePicker<T[K]> ? RecursivePick<T[K], KOK> :
never : never : never }, keyof T & keyof KO>;
它基本上遍历了以下属性T
并检查相应的属性KO
。如果该房产是true
它返回的属性T
不变。如果属性本身就是一袋属性,那么它会向下递归。如果该属性不存在,则会返回never
。整件事就是Pick
ed,以便只有键同时出现在两者中T
and KO
出现在最终输出中(这有点麻烦,以确保所有相关的映射类型 https://www.typescriptlang.org/docs/handbook/advanced-types.html#mapped-types是同态的,意味着可选属性保持可选)。
让我们验证一下它是否有效:
type ExpectedType = RecursivePick<Schema, typeof wantedSubset>;
您可以完成这个过程,但让编译器验证它:
type ExpectedTypeManual = {
user: {
name: string;
profile: {
isCool?: boolean;
};
};
};
type MutuallyExtends<T extends U, U extends V, V=T> = true
// if no error in the next line, then ExpectedType and ExpectedTypeManual are
// structurally the same
type NoErrorHere = MutuallyExtends<ExpectedType, ExpectedTypeManual>
这样一切都有效。您的函数将键入如下内容:
declare function gql<Q extends RecursivePicker<S>, S>(
query: Q | true,
schema: S
): RecursivePick<S, Q>;
const result = gql(wantedSubset, null! as Schema); // looks good
使函数的实现编译无错误可能需要overload https://www.typescriptlang.org/docs/handbook/functions.html#overloads因为条件类型很难推断:
function gql<Q extends RecursivePicker<S>, S>(
query: Q | true,
schema: S
): RecursivePick<S, Q>;
function gql(query: any, schema: object): object {
return {}; // something better here
}
好吧,希望有帮助;祝你好运!