使用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仅适用于作为列表的子项(在这种情况下,它将始终为true)
  • 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[]

然而,当我在a上将data: {}更改为list: true时,即使node['a']不是数组,也不会出现错误。我从一开始就知道我做错了什么,但我想知道TypeScript中是否可能出现这样的情况。我知道有了keyof TT[K]等许多新功能,我们就能够描述一些非常复杂的类型,所以我希望能够实现我想要做的事情

我的目标是设置编译时检查,这样开发人员就不会犯错误。显然,这些都不需要在运行时应用,因为类型都被剥离了。

编辑:基本上,如果有一种方法让我对V = T[K]施加约束,即V必须是特定类型,这个问题就会得到解决。

因此,事实证明,这目前可以通过映射类型来解决!并且无需定义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
}

有点冗长,但效果很好!可以推断出加号函数类型参数。

最新更新