簡體   English   中英

有沒有辦法定義一個 typescript 類型遞歸縮小所有后代的類型

[英]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;
    };
};
} */

這看起來不錯,雖然rootNodechildren屬性的類型顯示有點混亂。 ...[]是什么意思?

好吧,問題是因為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元素,以及正確形狀的userspouse屬性。

為了確保AccessibleFamilyTreeAccessibleFamilyTreeManual被認為是等價的,讓我們在它們之間做一些分配:

declare let aftm: AccessibleFamilyTreeManual;
aft = aftm; // okay
aftm = aft; // okay

這些賦值成功的表面表明編譯器認為它們的類型可以根據需要相互賦值。

游樂場代碼鏈接

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM