使用泛型在Typescript类中使用动态键

1sbrub3j  于 2023-05-30  发布在  TypeScript
关注(0)|答案(1)|浏览(158)

我在使用extends keyof时遇到了TS中类型系统的问题。在本例中,我有一个TreeContent对象,但我希望树允许动态键,以指示构造函数使用的树对象中保存数据的位置。
我就像这样给全班打电话

const tree = new Tree<
            TestTree,
            "children",
            "data",
            TestTreeData,
            "label"
        >(treeTestData, "children", "data", "label");

children键用于访问TestTree中的子数据,datalabel也是如此,但是label将用于查找TestTreeData中的label。我在这里的目标是创建一个灵活的类,它可以从树数据中构造一个n叉树,可以为子节点和它存储的数据使用不同的别名。

export class Tree<
    TreeContent,
    ChildKey extends keyof TreeContent,
    DataKey extends keyof TreeContent,
    Data,
    LabelKey extends keyof Data
>

{
    id: string;
    children: NodeModel<Data>[];
    childrenKey: ChildKey;
    dataKey: DataKey;
    labelKey: LabelKey;
    data: Data;

constructor(
        treeContent: TreeContent,
        _childrenKey: ChildKey,
        _dataKey: DataKey,
        _labelKey: LabelKey,
        _id = uuidv4()
    ) {
        this.dataKey = _dataKey;
        this.childrenKey = _childrenKey;
        this.labelKey = _labelKey;
        this.children = [];
        this.id = _id;

       if (
           !(_childrenKey in treeContent) ||
           !Array.isArray(treeContent[this.childrenKey])
          ) {
              throw Error(`Could not find data in ${String(this.childrenKey)}`);
          }

          const children = treeContent[this.childrenKey];

}

我省略了构造函数中的一些其他代码,因为我觉得它与问题无关。
问题是,children is const children: TreeContent[ChildKey]不是数组的类型,即使我显式检查它是否是数组。我知道我可以铸造它,但我正在尽我所能,以避免铸造在我的情况。如果这是我剩下的选择,那很好,但我不知道我做错了什么。
我怀疑我可能完全走错路了。
我想要的是,我可以通知TS,我希望const children: TreeContent[ChildKey]的值变成const children: TreeContent[],然后我可以用DataKey复制逻辑,但TreeContent[DataKey]应该等于Generic数据对象。
编辑:这里是一个TSPlayground与错误显示,我不能使用推功能。

wlzqhblo

wlzqhblo1#

您已经知道ChildKeyDataKeygeneric类型参数被约束为TreeContent类型参数的键。
对于DataKey键上的值类型没有任何进一步的约束,因此没有明显的理由使用Data类型参数all all;相反,您可以使用indexed access typeTreeContent[DataKey]来引用您调用的Data类型。
另一方面,您需要约束ChildKey键处的值类型,使其成为TreeContent数组。可以通过使用Record<K, V>实用程序类型将TreeContent约束为Record<ChildKey, TreeContent[]>来实现这一点。(这并不意味着TreeContent不能有比ChildKey更多的属性;对象类型在TS中是开放的,并且被允许具有比所提到的更多的属性。这只是意味着ChildKey必须是其中一个键,并且它的属性必须可分配给TreeContent[]
一旦你做了这些改变,事情就应该开始按你想要的那样运行了:

class Tree<
    TreeContent extends Record<ChildKey, TreeContent[]>,
    ChildKey extends keyof TreeContent,
    DataKey extends keyof TreeContent,
    LabelKey extends keyof TreeContent[DataKey]
>{
    children: TreeContent[];
    childrenKey: ChildKey;
    dataKey: DataKey;
    labelKey: LabelKey;
    data: TreeContent[DataKey];

    constructor(treeContent: TreeContent, _childrenKey: ChildKey,
        _dataKey: DataKey, _labelKey: LabelKey ) {
        this.dataKey = _dataKey;
        this.childrenKey = _childrenKey;
        this.labelKey = _labelKey;
        this.children = [];
    
        const children = treeContent[this.childrenKey];
        // const children: TreeContent[ChildKey]    
        children.push(); // okay
    
        this.data = treeContent[_dataKey]; // okay
    }
}

function createTree() {
    const tree = new Tree(testTreeData, "children", "data", "label");
    // const tree: Tree<TestTree, "children", "data", "label">
}

注意,在调用Tree构造函数时,不需要手动指定类型参数。编译器会自动为您正确推断它们。
Playground链接到代码

相关问题