【问题标题】:Typescript: How to declare a Type which represents a Treeview? Is it possible?Typescript:如何声明代表 Treeview 的类型?是否可以?
【发布时间】:2018-08-30 14:32:02
【问题描述】:

Typescript 是 JavaScript 的 TYPED 超集,可以编译成 JavaScript,很好! 它可以帮助我们减少一些错别字等,好的! 我想创建一个接口,该接口将用作方法中的参数。这个接口必须代表一个用于解析对象的树视图。

例子:w1[a2].x[b02].y.zmyObjectz值的访问路径

const path = "w1[a2].x[b02].y.z";
const treeOfIdentifiers = {
  "w1": {
    key: "idLvlW",
    "x": {
      key: "idLvlX"
    }
  }
}

const myObject = {
    w0: "Hello Root",
    w1: [{
        idLvlW: "a1",
        x: [{
            idLvlX: "b01",
            y: {
              z: "hello world from w1[a1].x[b01].y.z"
            }
          },
          {
            idLvlX: "b02",
            y: {
              z: "hello world from w1[a1].x[b02].y.z"
            }
          },
          {
            idLvlX: "b03",
            y: {
              z: "hello world from w1[a1].x[b03].y.z"
            }
          },
          {
            idLvlX: "b04",
            y: {
              z: "hello world from w1[a1].x[b04].y.z"
            }
          }
        ]
      },
      {
        idLvlW: "a2",
        x: [{
            idLvlX: "b01",
            y: {
              z: "hello world from w1[a2].x[b01].y.z"
            }
          },
          {
            idLvlX: "b02",
            y: {
              z: "hello world from w1[a2].x[b02].y.z"
            }
          },
          {
            idLvlX: "b03",
            y: {
              z: "hello world from w1[a2].x[b03].y.z"
            }
          },
          {
            idLvlX: "b04",
            y: {
              z: "hello world from w1[a2].x[b04].y.z"
            }
          }
        ]
      },
      {
        idLvlW: "a3",
        x: [{
            idLvlX: "b01",
            y: {
              z: "hello world from w1[a3].x[b01].y.z"
            }
          },
          {
            idLvlX: "b02",
            y: {
              z: "hello world from w1[a3].x[b02].y.z"
            }
          },
          {
            idLvlX: "b03",
            y: {
              z: "hello world from w1[a3].x[b03].y.z"
            }
          },
          {
            idLvlX: "b04",
            y: {
              z: "hello world from w1[a3].x[b04].y.z"
            }
          }
        ]
      }

    ]

如果我使用 TypeScript 编码,treeOfIdentifiers 的类型|接口(如果不是 any!)会是什么? 事情是要确保 treeOfIdentifiers 的每个节点,将提供key 属性,我们不知道treeOfIdentifiers 的结构,因为我们不知道要解析的对象的结构!

【问题讨论】:

  • 所以你有一个带有键和一些值的节点。这个值可以是另一个节点还是某个字符串?对吗?
  • 我的意思是这个值可以是一个节点数组或一些字符串?
  • @FortyTwo 键是我们可以得到的确切属性,而其他键是与要解析的对象中的数组属性一样多的节点。
  • 如果我对您的理解正确,那么像 INode { id: string; data: INode[] | IData[]; }IData { data: string; } 这样的东西应该可以工作
  • treeOfIdentifiers 的每个节点是否总是恰好有两个属性,一个是具有string 值的key,另一个是值是另一个节点的其他字符串键?还是我错过了什么?

标签: typescript


【解决方案1】:

这在 TypeScript 中是一件相当困难的事情。例如,将名为key 的字符串值属性添加到非string 值的字典中对于强类型来说并不简单......并且可能意味着您可能希望使类型更简单(例如,每个节点都有key 属性和 dictionary 属性)。

我什至没有考虑确保treeOfIdentifiers 在使用它遍历myObject 时有效,因为您没有问过这个问题,这已经很复杂了。

但是让我们看看使用mappedconditional 类型可以走多远:

type NonRootOfTreeOfIdentifiers<T> = { [K in 'key' | keyof T]:
  K extends 'key' ? string :
  K extends keyof T ? NonRootOfTreeOfIdentifiers<T[K]> : never
};
type TreeOfIdentifiers<T> = { [K in keyof T]: NonRootOfTreeOfIdentifiers<T[K]> };
const asTreeOfIdentifiers = <T extends TreeOfIdentifiers<T>>(t: T): T => t;

其中最棘手的部分是NonRootOfTreeOfIdentifiers&lt;T&gt;。它采用类型T 表示有效treeOfIdentifiers 中的非根节点。它将key 属性添加到T,然后将T 的这些属性映射到一个新类型:key 被映射为string 属性,所有其他属性都映射到NonRootOfTreeOfIdentifiers&lt;&gt; 版本本身。所以NonRootOfTreeOfIdentifiers&lt;T&gt; 向下遍历T 的级别并将其转换为另一种类型。如果T 是一个有效的非根节点,那么NonRootOfTreeOfIdentifiers&lt;T&gt; 将是一个有效的非根节点。

TreeOfIdentifiers 类型函数表示根节点,它不需要key 属性,但否则会向下遍历到NonRootOfTreeOfIdentifiers

最后,asTreeOfIdentifiers 函数接受一个需要与TreeOfIdentifiers&lt;T&gt; 兼容的T 类型的参数,并返回该参数。意思是,它只会接受符合规则的对象。所以它验证了它的论点,但没有改变它。

让我们看看它是否有效:

const treeOfIdentifiers = asTreeOfIdentifiers({
  "w1": {
    key: "idLvlW",
    "x": {
      key: "idLvlX"
    }
  }
});  

编译,treeOfIdentifiers 被推断为类型

{
    "w1": {
        key: string;
        "x": {
            key: string;
        };
    };
}

如果你以某种方式搞砸了,你会得到错误:

const missingKey = asTreeOfIdentifiers({
  "w1": {
    key: "idLvlW",
    "x": {
      kee: "idLvlX" // oops
    }
  }
}); 
// error: Property 'key' is missing in type '{ kee: string; }'

const extraProperty = asTreeOfIdentifiers({
  "w1": {
    key: "idLvlW",    
    x: {
      key: "idLvlX"
    }
    y: 123 // oops
  }
}); 
// error: Types of property 'y' are incompatible.
//  Type 'number' is not assignable to type 'NonRootOfTreeOfIdentifiers<number>'.

这样就可以了。不过,我不确定这对你是否值得。如果您尝试捕获像 "idLvlX" 这样的文字字符串值类型(而不仅仅是 string),它会变得更加棘手。而且,正如我所说,我什至没有想过如何在类型系统中验证myObjecttreeOfIdentifiers

不管怎样,祝你好运。希望对您有所帮助。

【讨论】:

  • 一点也不差,谢谢!这也不行: const extraProperty = asTreeOfIdentifiers({ key:"id", "w1": { key: "idLvlW", x: { key: "idLvlX" } } });
猜你喜欢
  • 1970-01-01
  • 2019-12-09
  • 1970-01-01
  • 1970-01-01
  • 2020-11-18
  • 1970-01-01
  • 2016-03-12
  • 2021-05-03
  • 1970-01-01
相关资源
最近更新 更多