2017-11-10 20 views
2

我正在嘗試改編我已經在TypeScript中編寫的現有流程,使其具有更強的類型。使用TypeScript指定複雜的樹處理規則

基本上,我有一個樹狀結構,看起來像這樣:

interface Node { 
    [name: string]: Leaf | Node | Node[] | undefined; 
} 

這是一個簡單的樹,其中每個節點都包含無論是葉節點,另一個節點,節點列表,或不確定的,由組織屬性名稱。

我有一個機制,可以在這棵樹上運行規則。規則是這樣的:

interface Rule<T extends Node, K = keyof T> { 
    name: K; 
    optional?: boolean; 
    data?: {}; // matches against the structure of a child Leaf 
    list?: boolean; // whether this child should be a list 
    process?(processor: RuleProcessor): Node; // for processing sub-nodes 
    // ...other flags and stuff specific to the rule 
} 

我想通過指定適用於不同類型的兒童單獨類型的規則,使這個更通用:

  • optional僅適用於兒童,可能是未定義
  • data僅適用於有葉的孩子
  • list只適用於那些列表(在這種情況下,它總是爲真)
  • 兒童
  • process()只適用於那些節點或節點列表兒童

這就是我設想:

type Child = Leaf | Node | Node[]; 

interface Node { 
    [name: string]: Child | undefined; 
} 

interface Rule<T extends Node, K extends keyof T = keyof T, _V extends T[K] = T[K]> { 
    name: K; 
} 

interface OptionalRule<T extends Node, K extends keyof T = keyof T> extends Rule<T, K, Child | undefined> { 
    optional: true; 
} 

interface RequiredRule<T extends Node, K extends keyof T = keyof T, V extends Child = Child> extends Rule<T, K, V> {} 

interface LeafRule<T extends Node, K extends keyof T = keyof T> extends RequiredRule<T, K, Leaf> { 
    data: {}; 
} 

interface ListRule<T extends Node, K extends keyof T = keyof T> extends RequiredRule<T, K, Node[]> { 
    list: true; 
} 

interface ProcessRule<T extends Node, K extends keyof T = keyof T> extends RequiredRule<T, K, Node | Node[]> { 
    process(processor: RuleProcessor): Node; 
} 

type AnyRule<T extends Node> = OptionalRule<T> | LeafRule<T> | ListRule<T> | ProcessRule<T>; 

function process<T extends Node>(node: T, rules: AnyRule<T>[]) { 
    // logic 
} 

的想法是,如果我指定的規則特定屬性,該選擇的規則類型將會檢查該屬性的類型。

例如,說我有一個節點:

const node: Node = { a: new Leaf(), b: [] }; 

我對其進行處理:

process(node, [{ name: 'a', data: {} }, { b: list: true }]); 

我期望指定data將檢查以某種方式確保node['a']Leaf,如果不是,則會發出錯誤。同樣,第二條規則會檢查以確保node['b']Node[]

但是,當我將data: {}更改爲list: true,a時,即使node['a']不是數組,也不會發生錯誤。我知道我從一開始就做錯了什麼,但是我想知道在TypeScript中是否有這樣的事情是可能的。我知道有很多這些新功能,如keyof TT[K],我們能夠描述一些相當複雜的類型,所以我希望能夠實現我想要做的。

我的目標是將編譯時間檢查到位,以便開發人員不會犯任何錯誤。很明顯,這些都不需要在運行時應用,因爲類型都被剝離了。

編輯:基本上這個問題將被解決,如果有一種方法可以讓我在V = T[K]上設置一個約束,即V必須是特定類型。

回答

0

所以,事實證明,這是目前可以解決的映射類型!而且也沒有必要定義Node類型:

type Child = Leaf | Node | Node[]; 

interface Rule<T extends { [P in K]: V }, K extends keyof T, V> { 
    name: K; 
} 

interface OptionalRule<T extends { [P in K]?: V }, K extends keyof T, V> extends Rule<T, K, Child | undefined> { 
    optional: true; 
} 

interface RequiredRule<T extends { [P in K]: V }, K extends keyof T, V extends Child> extends Rule<T, K, V> {} 

interface LeafRule<T extends { [P in K]: Leaf }, K extends keyof T> extends RequiredRule<T, K, Leaf> { 
    data: {}; 
} 

interface ListRule<T extends { [P in K]: V[] }, K extends keyof T, V> extends RequiredRule<T, K, V[]> { 
    list: true; 
} 

interface ProcessRule<T extends { [P in K]: V | V[] }, K extends keyof T, V> extends RequiredRule<T, K, V | V[]> { 
    process(processor: RuleProcessor): V; 
} 

type AnyRule<T extends { [P in K]: V }, K extends keyof T, V> = OptionalRule<T, K, V> | LeafRule<T, K> | ListRule<T, K, V> | ProcessRule<T, K, V>; 

function process<T extends { [P in K]: V }, K extends keyof T, V>(node: T, rules: AnyRule<T, K, V>[]) { 
    // logic 
} 

有點冗長,但它工作正常! Plus函數類型參數可以被推斷。