在Typescript中是否可以创建类似于此类型的子集?
type Schema = {
user: {
name: string;
age: number;
profile: {
isCool?: boolean;
};
};
};
const wantedSubset = {
user: {
name: true,
profile: {
isCool: true
}
}
};
type ExpectedType = {
user: {
name: string;
profile: {
isCool?: boolean;
};
};
};
const result: ExpectedType = desiredCode(wantedSubset);
在不清楚的情况下,我想拥有desiredCode
函数,给定对象wantedSubset
将返回除键入为declare function pick<T, K extends keyof T>(obj: T, ...keys: K[]): Pick<T, K>;
的递归使用之外的任何内容
我想将此Pick
递归地应用于Schema
,但是我不知道如何递归地“引用” Schema
类型。
我尝试过这样的事情:
function gql<Q, S, QK extends keyof Q, SK extends keyof S, K extends SK & QK>(
query: Q | true,
schema: S
) {
if (query === true) return (query as unknown) as S;
const keys = Object.keys(query) as K[];
const result = {} as Pick<S, K>;
keys.forEach(k => {
result[k] = gql(query[k], schema[k]);
});
return result;
}
const result = gql(wantedSubset, wantedSubset as unknown as Schema)
但是它不能按照我想要的方式工作。它只是返回Pick的结果,而不是递归地应用它。
基本上,问题在于如何动态地构建对象,以便打字稿能够推断出返回值。
答案 0 :(得分:2)
因此,我认为您希望wantedSubset
符合一种类型,其中每个属性均为true
或本身就是该类型的对象。我们需要做一些工作才能使TypeScript推断出这样一种类型,其中将值true
视为类型true
而不是boolean
(至少直到TS3.4出来并给我们const
contexts):
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
}
因此,如果T
是Schema
,那么RecursivePicker<Schema>
会给您:
type RPSchema = RecursivePicker<Schema>
// type RPSchema = {
// user?: {
// age?: true;
// name?: true;
// profile?: {
// isCool?: true;
// }
// }
// }
这可能是对wantedSubset
类型的约束,您可以从Schema
类型中进行选择。
这是RecursivePick
的所有递归映射条件恐怖荣耀:
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
的,因此只有出现在T
和KO
中的键才会出现在最终输出中(这是在摆弄所有相关的{{ 3}}是同态的,这意味着可选属性保持可选)。
让我们验证一下它是否有效:
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
要获得无错误编译的函数实现,可能需要mapped types,因为众所周知,条件类型很难推断:
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
}
好的,希望能有所帮助;祝你好运!