TypeScript:如何正确键入此树节点,使其具有大小属性或子属性?



我有一个树节点类型。

type Node = {
name: string,
size?: number,
children?: Node[]
}

这里的想法是,对于叶节点,它应该具有size属性,但没有children属性。对于具有子节点的节点,它应该具有children属性,但没有size属性。

现在我将它们都标记为可选,但实际上,它们具有非此即彼的关系。

如何调整它以正确键入这样的节点。

为了使它起作用,您希望Node成为联合类型,其中联合的每个成员显式包含一个属性并显式禁止另一个属性。 它可能看起来像这样:

interface BaseNode {
name: string;
}
interface LeafNode extends BaseNode {
size: number;
children?: never
}
interface InternalNode extends BaseNode {
size?: never;
children: Node[]
}
type Node = LeafNode | InternalNode;

这里的NodeLeafNodeInternalNode的并集,两者都继承自具有公共name属性的公共BaseNode类型(其他公共属性将在那里)。LeafNode类型要求size并禁止children,而InternalNode需要children并禁止size

好吧,TypeScript 实际上并不直接支持禁止属性的概念。 相反,你可以说一个属性是可选的(所以它被允许不存在),并且它的类型是不可能的never类型(所以你永远不会找到它允许具有的值)。 这是我们所能达到的尽可能接近的程度。


让我们测试一下:

const node: Node = {
name: "root",
children: [
{ name: "tooMany", size: 1, children: [] }, // error
{ name: "tooFew" } // error
]
}

看起来不错。"root"节点没问题,因为它有children但没有size"leaf"节点没问题,因为它有size但没有children。 另一方面,"tooMany"节点产生编译器错误,因为它同时具有sizechildren,而"tooFew"节点产生编译器错误,因为它既没有size也没有children

另请注意,您可以将Node视为可区分的并集,只要您的检查是查看属性是否已定义或undefined

function processNode(node: Node): number[] {
return node.children ?
node.children.flatMap(processNode) : [node.size];
}

操场链接到代码

最新更新