【问题标题】:Typescript - How to use nested mapped typesTypescript - 如何使用嵌套映射类型
【发布时间】:2019-10-04 17:13:02
【问题描述】:

我有一个无法修改的大型复杂接口 X,我想根据接口 X 中的属性和子属性定义一些较小的类型,我想将其用作函数参数的类型。我还想避免单独重新声明 X 中已有的内容。

interface X {
  a:number;
  c:{
    aa:number;
    cc:{
      aaa:string;
    }
  },
  d?:{
    aa:number;
    cc:{
      aaa:string;
    }
  },
  e:{
    aa:number;
    cc:{
      aaa:string;
    }
  }[]
}

我知道我可以使用映射类型来访问子类型[编辑注释:子类型是错误的术语,这应该称为嵌套级别类型,请参阅下面的答案] 这应该是对象,例如:

type C = X["c"]; // { aa:number; cc:{ aaa:string } } 

type C_CC = X["c"]["cc"]; // { aaa: string }

type D = X["d"]; // { aa:number; cc:{ aaa:string } } | undefined

但是,当属性可能未定义时,事情变得有点棘手。如果我想获得 X.d.cc 的类型...(应该给出 {aaa:string} )我该怎么做?还是有可能?

以下两次尝试都报错:

type D_CC1 = X["d"]["cc"];  // error? Due to the (d?) possibly undefined?
type D_CC2 = Required<X["d"]>["cc"];  // error also?

另外,当涉及到数组时,我也有点困惑,例如上面的 E:

type E = X["e"]; // { aa:number; cc:{ aaa:string } }[]
type E_CC1 = X["e"]["cc"]; // error
type E_CC2 = X["e"][0]["cc"]; // got the result I wanted: { aaa: string }

第三行似乎有效,但我不确定使用 [0] 的逻辑是什么——看起来很老套。我想知道这是否是正确的方法。

编辑:将类型名称从 D_CC 更改为 D_CC1 和 D_CC2 以避免歧义。

【问题讨论】:

  • 我对 D_CC 没有任何错误。你确定你只是没有同时声明两者都使用相同的名称,因此是重复的标识符吗?
  • 我在 ["cc"] 下得到 typescript 波浪线并出现错误:类型 '{ aa: number; 上不存在属性 'cc'抄送:{aaa:字符串; }; } | undefined'.ts(2339). 但是输入 type D_CC3 = Required&lt;X&gt;["d"]["cc"]; 有效!但这是做事的正确方式吗(使用Required)?

标签: typescript


【解决方案1】:

只是一个细节,但为了不混淆术语,您在界面中拥有的不是子类型,而是嵌套级别的类型。嵌套级别类型具有顶级类型。子类型是您可以在需要具有此子类型的类型的任何地方使用的类型。

接下来,你所说的映射类型实际上就是我们所说的keying-in操作符。这是一种在形状中查找属性类型的方法。

映射类型用于映射对象的键和值类型,如下所示:

type MyMappedType = {
  [Key in K]: ValueType
}

这也是您可以利用 keyof 运算符的地方。 Key in keyof Something

例如,要将形状中的所有字段设为可选(为此有一个名为 Partial&lt;Object&gt; 的内置映射类型),您可以使用 mapped types

type ShapeAllOptional = {
  [K in keyof MyShape]?: MyShape[K]
}

现在回答你的问题。

type D_CC1 = X["d"]["cc"];  // error? Due to the (d?) possibly undefined?

你可以使用这样的东西

type myStype = NonNullable<X['d']>['cc']

关于使用

type E_CC1 = X["e"]["cc"]; // error
type E_CC2 = X["e"][0]["cc"]; // got the result I wanted: { aaa: string }

是的,它是一个数组,每个项目都具有该形状。我不知道这是否是 hacky,但我确实看到了一些看起来有点“干净”的东西。

type E_CC2 = X["e"][number]["cc"];

我知道没有更好的方法了。

啊,我差点忘了。

type D_CC2 = Required<X["d"]>["cc"];  // error also?

小心,您在这里所说的是使 X['d'] 中的所有属性都必需,即 aacc,它们是......已经必需的。 Required&lt;X&gt; 我想实现你想要的。

【讨论】:

  • 感谢您非常详细的解释 - 我现在完全理解了。我也喜欢你对 D_CC 使用条件类型的想法,因为使用 Required 似乎有点过头了,但我无法让你的代码工作:type D_CC = X['d'] extends undefined ? undefined : X['d']['cc']。尽管如此,你还是让我走上了正轨——我想像type D_CC =Exclude&lt;X['d'],undefined&gt;['cc'];这样的东西?至于使用 [0] 我也更喜欢 [number] .. 我理解为什么数组需要有索引,只是强制 0 似乎有点违反直觉!
  • 你是对的,我现在也遇到同样的错误。我虽然它会使用类型细化或类似的东西。您可以使用 NonNullable['cc'],而不是使用排除,更简洁:)。
  • 没有意识到NonNullable&lt;T&gt; 也排除了未定义。感谢那! :)
【解决方案2】:

如果你看一下你的界面,你会注意到e 属性实际上被定义为数组。

e:{
    aa:number;
    cc:{
      aaa:string;
    }
}[] // NOTICE THIS!

这就是您需要使用索引器[0] 来获取该数组的项目类型的原因。这不是hack,实际上是从2.1开始在typescript中添加的,正式名称是indexed access types, also called lookup types

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-08-14
    • 1970-01-01
    • 2021-09-16
    • 1970-01-01
    • 2018-06-25
    • 1970-01-01
    • 1970-01-01
    • 2020-08-29
    相关资源
    最近更新 更多