【问题标题】:TypeScript: interface depending on the types of a (recursive) propertyTypeScript:接口取决于(递归)属性的类型
【发布时间】:2020-09-18 04:35:39
【问题描述】:

我有一个树状数据结构。我想用节点包装器包装每个树节点,它为节点添加了额外的功能。但同时我想保持打字完整。有人可以帮我解决这个问题吗?

这是一个显示我想要实现的示例

const a = { children: [], value: 12 } as Node<number>;
const b = { children: [], value: 'string' } as Node<string>;
const c = { children: [b, a], value: true } as Node<boolean>;

const root = { children: [c]; value: null } as Node<null>;

// wrap node data with some functions, but keep typings stable
const wrappedNode = wrapNode(root);

wrappedNode.value; // type should be => null
wrappedNode.children[0].value; // type should be => boolean
wrappedNode.children[0].children[0].value; // type should be => string
wrappedNode.children[0].children[1].value; // type should be => number

我目前的方法如下:

interface Node<T, C extends Node<unknown, any[]>[]> {
  children: [...C];
  value: T;
}

interface WrapNode<T, C extends Node<unknown, any[]>[]> {
  children: WrapNode<any, [...C]>[];
  value: T;
  computeValue(): any;
}

function createNode<T, C extends Node<unknown, any[]>[]>(value: T, children: [...C]): Node<T, [...C]> {
  return {
    value,
    children,
  };
}

export function wrapNode<T, C extends Node<any, any>[]>(node: Node<T, C>): WrapNode<T, typeof node.children> {
  const value = node.value;

  return {
    ...node,
    computeValue: () => value,
    children: node.children.map(child => wrapNode(child)),
    //                          ^^^^^    ^^^^^^^^^^^^^^^
    //           types are:   C[number]  wrapNode<any, any>(n: Node<any, any>)
  };
}

const a = createNode(12, []);
const b = createNode('str', []);
const c = createNode(null, [b, a]);

const x = wrapNode(c);

x.value; // gives me type null, ok!
x.children[0].value; // gives me any :(
x.children[1].value; // gives me any too :(

使用 TypeScript 是否有可能?如果有帮助,我正在使用 TypeScript 4.0.2。在此先感谢:)

【问题讨论】:

    标签: typescript typescript-typings


    【解决方案1】:

    我想你可能希望你的WrapNode 界面看起来像这样:

    interface WrapNode<T, C extends Node<unknown, any[]>[]> {
        children: { [K in keyof C]:
            C[K] extends Node<infer CT, infer CC> ? WrapNode<CT, CC> : never
        }
        value: T,
        computeValue(): any,
    }
    

    这里的重要区别在于children 属性是一个mapped array/tuple type,它遍历children 的数字索引并获取每个元素的类型。

    编译器实际上验证任何特定值可分配给这种映射类型的可能性不大,因此您可能希望在 wrapNode() 实现中使用 type assertion

    function wrapNode<T, C extends Node<unknown, any[]>[]>(node: Node<T, C>): WrapNode<T, C> {
        const value = node.value;
        return {
            ...node,
            computeValue: () => value,
            children: node.children.map(wrapNode) as any, // need to assert here
        };
    }
    

    然后一切都如你所愿,我认为:

    const x = wrapNode(c);
    x.children[0].value.toUpperCase(); // okay
    x.children[0].value.toFixed(2); // error! string can't do that
    x.children[1].value.toFixed(2); // okay
    x.children[1].value.toUpperCase(); // error! number can't do that
    

    Playground link to code

    【讨论】:

    • 明天去测试,太好了!非常感谢。 :) 我一测试就会接受 :)
    • 很好,工作正常 :) 你能详细说明一下“类型断言”部分吗?你的意思是像(在 wrapNode 的返回中):return { /*...*/, children: node.children.filter(isNode).map(wrapNode)},其中isNode 有签名isNode(value: any): value is Node
    • 我有一个后续问题:stackoverflow.com/questions/63952570/…,如果你不介意的话:)
    • 类型断言是children 行中的as any。你不能用isNode()修复它:问题不在于node.children可能没有Nodes。问题是编译器只知道node.childen.map(wrapNode) 会产生一个WrapNode&lt;unknown, any&gt;s 的数组。它不明白对于每个索引i,如果node.children[i]Node&lt;CT, CC&gt; 类型,那么node.children.map(wrapNode)[i] 将是相应类型WrapNode&lt;CT, CC&gt;。这对编译器来说太高阶了。
    猜你喜欢
    • 2021-06-28
    • 1970-01-01
    • 2020-02-28
    • 1970-01-01
    • 2021-01-18
    • 2020-01-07
    • 1970-01-01
    • 1970-01-01
    • 2016-07-18
    相关资源
    最近更新 更多