[英]Is there a way to define a typescript type which recursively narrows the type of all descendents
我有一堆 typescript 類型,它們使用共享的Inaccessible
接口代替用戶無權訪問的對象。
但是,通常我使用的類型比上面的類型(來自 GQL 端點的嵌套數據)更復雜,並且我正在編寫的組件只有在定義了整個 object 時才能工作。 因為這很常見,所以我希望能夠編寫一個類型保護器,我可以在任何組件中使用它,並遞歸地從基類型的所有后代中“刪除”所有Inaccessible
類型。
這是我正在嘗試做的一個例子。 但至關重要的是, AllPropertiesAccessible<T>
類型將是通用的,因此它可以與具有 Inaccessible 后代的其他類型一起使用。
interface Inaccessible {
invalidId: string;
}
interface Person {
id: string;
name: string;
}
interface Node {
children: Array<Node | Inaccessible>;
user: Person | Inaccessible;
spouse: Person | Inaccessible;
}
interface FamilyTree {
rootNode: Node | Inaccessible;
}
function isFamilyTreeAccessible(familyTree: FamilyTree) familyTree is AllPropertiesAccessible<FamilyTree> {
// logic which recursively validates that all elements in the tree are accessible
}
function FamilyTreeOrInaccessibleDisplay({familyTree}: {familyTree: FamilyTree}): JSX.Element {
if (isFamilyTreeAccessible(familyTree)) {
return <FamilyTreeDisplay familyTree={familyTree} />
} else {
return <NotAccessibleMessage />
}
}
function FamilyTreeDisplay({familyTree}: {familyTree: AllPropertiesAccessible<FamilyTree>}) {}
function NotAccessibleMessage() {
return <div>Inaccessible</div>
在這個例子中,我們有一個FamilyTree
類型,它有一堆可能無法訪問的后代。 AllPropertiesAccessible<FamilyTree>
類型等同於:
interface AccessibleNode {
children: Array<AccessibleNode>;
user: Person;
spouse: Person;
}
interface AccessibleFamilyTree {
rootNode: AccessibleNode;
}
有沒有辦法在typescript中寫出這樣一個AllPropertiesAccessible
類型?
鑒於您的示例代碼,我傾向於將AllPropertiesAccessible
定義為以下遞歸條件類型:
type AllPropertiesAccessible<T> =
T extends Inaccessible ? never :
{ [K in keyof T]: AllPropertiesAccessible<T[K]> }
它是一種分配條件類型,因此它將T
拆分為其聯合成員; 它們中的任何一個Inaccessible
都將被刪除(因為它們評估為不可能的never
type ),而那些未被刪除的將被映射,因此任何屬性本身都會使用AllPropertiesAccessible<>
進行轉換。
這個定義看似簡單,因為你可能想知道如果T
是像string
這樣的原始類型(是否映射了它的所有明顯屬性,如length
?)或者如果T
是一個數組(是否映射了它的所有數組方法和屬性,如length
)會發生什么?)。 幸運的是,由於{[K in keyof T]: AllPropertiesAccessible<T[K]>}
是同態映射類型(請參閱“同態映射類型”是什么意思? ),它會自動單獨保留原語(因此AllPropertiesAccessible<string>
將是string
) 並自動將數組/元組映射到數組/元組) (因此AllPropertiesAccessible<T[]>
將與AllPropertiesAccessible<T>[]
相同)。
讓我們測試一下:
type AccessibleFamilyTree = AllPropertiesAccessible<FamilyTree>;
/* type AccessibleFamilyTree = {
rootNode: {
children: ...[];
user: {
id: string;
name: string;
};
spouse: {
id: string;
name: string;
};
};
} */
這看起來不錯,雖然rootNode
的children
屬性的類型顯示有點混亂。 ...[]
是什么意思?
好吧,問題是因為FamilyTree
是遞歸類型, AccessibleFamilyTree
也是遞歸的。 但是AllPropertiesAccessible<T>
類型不會為轉換生成的中間類型賦予新名稱。 事實上,如果你自己寫出預期的類型,你會發現自己正在尋找一個新的命名類型:
interface AccessibleFamilyTreeManual {
rootNode: AccessibleNode;
}
// need to name this
interface AccessibleNode {
children: Array<AccessibleNode>;
user: Person;
spouse: Person;
}
如果你試圖讓AccessibleNode
保持匿名,你最終會無限倒退。 編譯器通過在顯示類型時放棄來處理這個問題。 它仍然知道它是什么:
declare let aft: AccessibleFamilyTree;
const foo = aft.rootNode.children.map(x => x.user.name)
// ---------------------------------> ^
// (parameter) x: {
// children: ...[];
// user: { id: string; name: string; };
// spouse: { id: string; name: string; };
// }
// const foo: string[]
aft.rootNode.children
的每個元素的類型都是已知的,它們具有某種遞歸數組類型的children
元素,以及正確形狀的user
和spouse
屬性。
為了確保AccessibleFamilyTree
和AccessibleFamilyTreeManual
被認為是等價的,讓我們在它們之間做一些分配:
declare let aftm: AccessibleFamilyTreeManual;
aft = aftm; // okay
aftm = aft; // okay
這些賦值成功的表面表明編譯器認為它們的類型可以根據需要相互賦值。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.