[英]Typescript recursive subset of type
在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>;
遞歸使用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的結果,而不是遞歸地應用它。
基本上,問題是如何動態構建對象,以便打字稿能夠推斷出返回值。
https://www.typescriptlang.org/play/#src=type%20Schema%20%3D%20%7B%0D%0A%20%20user%3A%20%7B%0D%0A%20%20%20%20 %20name%3A%20string%3B%0D%0A%20%20%20%20age%3A%20number%3B%0D%0A%20%20%20%20%profile%3A%20%7B%0D%0A%20 %20%20%20%20%20isCool%3F%3A%20boolean%3B%0D%0A%20%20%20%20%7D%3B%0D%0A%20%20%7D%2C%0D%0A %20%20foo%3A%20%7B%0D%0A%20%20%20%20bar%3A%20string%3B%0D%0A%20%20%7D%0D%0A%7D%3B%0D%0A %0D%0Aconst%20wantedSubset%20%3D%20%7B%0D%0A%20%20user%3A%20%7B%0D%0A%20%20%20%20%20name%3A%20true%2C%0D%0A %20%20%20%20profile%3A%20%7B%0D%0A%20%20%20%20%20%20isCool%3A%20true%0D%0A%20%20%20%20%20%7D%0D %0A%20%20%7D%0D%0A%7D%3B%0D%0A%0D%0A%0D%0Afunction%20gql%3CQ%2C%20S%2C%20QK%20擴展了%20keyof%20Q%2C%20SK %20extends%20keyof%20S%2C%20K%20extends%20SK%20%26%20QK%3E(%0D%0A%20%20query%3A%20Q%20%7C%20true%2C%0D%0A%20% 20schema%3A%20S%0D%0A)%20%7B%0D%0A%20%20if%20(query%20%3D%3D%3D%20true)%20return%20(query%20as%20unknown)%20as %20S%3B%0D%0A%20%20const%20keys%20%3D%20Object.keys(query)%20as%20K%5B%5D%3B%0D%0A%20%20const20%re sult%20%3D%20%7B%7D%20as%20Pick%3CS%2C%20K%3E%3B%0D%0A%20%20keys.forEach(k%20%3D%3E%20%7B%0D% 0A%20%20%20%20result%5Bk%5D%20%3D%20gql(query%5Bk%5D%2C%20schema%5Bk%5D)%3B%0D%0A%20%20%7D)%3B% 0D%0A%0D%0A%20%20return%20result%3B%0D%0A%7D%0D%0A%0D%0Aconst%20result%20%3D%20gql(wantedSubset%2C%20wantedSubset%20as%20unknown%20as% 20Schema)%0D%0A%0D%0Aresult.user.age%20%2F%2F%20should%20be%20an%20error!%0D%0Aresult.user.name%20%2F%2F%20works%20OK%0D% 0Aresult.user.profile.isCool%20%2F%2F%20works%20OK%0D%0Aresult.foo%20%2F%2F%20works%20like%20expected
因此,我認為您希望wantedSubset
符合每種屬性為true
或本身為該類型的對象的類型。 我們需要做一些工作才能使TypeScript推斷出這樣一種類型,其中將值true
視為類型true
而不是boolean
(至少直到TS3.4出來並為我們提供const
上下文之前 ):
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
和friends類型。 首先,給定類型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;
// }
// }
// }
這可能是對您想要從Schema
類型中選擇的wantedSubset
類型的限制。
這是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
ed,這樣只有T
和KO
中同時出現的鍵才會出現在最終輸出中(這有點擺弄,以確保所有相關的映射類型都是同態的,這意味着可選屬性保持可選)。
讓我們驗證一下它是否有效:
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
要使函數的實現沒有錯誤地進行編譯,可能需要重載,因為眾所周知很難推斷條件類型:
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
}
好的,希望能有所幫助; 祝好運!
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.