【问题标题】:How can i move away from a Partial<T> to T without casting in Typescript如何在不使用 Typescript 的情况下从 Partial<T> 移动到 T
【发布时间】:2020-03-17 19:13:03
【问题描述】:

一般 Typescript 问题说我迭代了一个我知道其内容的数组并应用 reduce 来获取一个我知道类型的对象 例如:

interface IMyInterface {
  a: number;
  b: number;
  c: number;
}
const result: IMyInterface = ['a','b','c'].reduce((acc: Partial<IMyInterface>,val)=>({...acc,[val]: 1}), {});

现在这行不通了,因为结果应该是Partial&lt;IMyInterface&gt;,这是有道理的,考虑到 TS 不能告诉数组的内容将产生“完整”对象。 但是我需要做什么才能使结果可以是 IMyInterface 类型而不需要 as IMyInterface

这是一个回复https://repl.it/@Sudakatux/KaleidoscopicGraciousApplicationpackage

提前致谢

【问题讨论】:

  • reduce()?没有什么比类型断言更好的了。为了使它起作用,回调需要是通用的&lt;K extends keyof IMyInterface, T extends Partial&lt;IMyInterface&gt;&gt;(acc: T, val: K): T &amp; Record&lt;K, number&gt; =&gt; ...,但是类型系统缺乏表达为元组类型["a","b","c"]的每个元素指定TK的高阶操作的能力.
  • 你可以unroll它,但不能循环或reduce()
  • @jcalz 我不明白这有什么关系。对不起。在展开示例中,有一个转换为 any,然后转换为从 any 到 Record
  • 这不是 important 的展开示例,抱歉
  • 这里有更多analogous unrolled example。这很有效,但这只是因为编译器认为每个步骤都是单独发生的。

标签: typescript


【解决方案1】:

这里的简短回答是:您几乎需要使用type assertion,因为编译器不可能确定您正在做的事情是安全的。


更长的答案:为了让编译器知道发生了什么,你需要回调是generic。这是一种输入方式:

const cb = <K extends keyof IMyInterface, T extends Partial<IMyInterface>>(
    acc: T, val: K): T & Record<K, number> => ({ ...acc, [val]: 1 })

该类型签名表明cb 有两个参数,accvalacc 参数是泛型类型T,必须可分配给Partial&lt;IMyInterface&gt;val 参数是泛型类型K,必须可分配给keyof IMyInterface。那么回调的输出是T &amp; Record&lt;K, number&gt;:也就是说,它是一个对象,拥有来自T的所有键和值,但它在键K处也有一个确定的number值。因此,当您调用cb() 时,返回值可能与acc 的类型不同。

这为编译器提供了足够的信息,让您可以避免类型断言...但前提是您手动使用cb() 执行类似reduce() 的操作,通过将循环展开为一堆嵌套调用:

const result: IMyInterface = cb(cb(cb({}, "a"), "b"), "c"); // okay
const stillOkay: IMyInterface = cb(cb(cb({}, "a"), "c"), "b"); // okay
const mistake: IMyInterface = cb(cb(cb({}, "b"), "b"), "c"); // error! property "a" is missing

在这里你可以看到编译器真的在寻找你,因为如果你以错误的方式调用cb(),你会得到一个错误告诉你。

很遗憾,the type signature for Array&lt;T&gt;.reduce()

reduce<U>(
  callbackfn: (previousValue: U, currentValue: T, currentIndex: number, array: readonly T[]) => U, 
  initialValue: U
): U;

不足以表示每次在数组元素上调用 callbackfn 时发生的连续类型缩小。据我所知,没有办法改变它来做到这一点。你想说callbackfn 类型是一些类型的疯狂交集,对应于它对数组的每个连续成员的行为方式,例如((p: A, c: this[0])=&gt;B) &amp; ((p: B, c: this[1])=&gt;C) &amp; ((p: C, c: this[2])=&gt;D) &amp; ...,对于泛型参数ABC、@ 987654354@等,然后希望编译器可以从你对reduce()的调用中推断出这些参数。好吧,它不能。 higher order inference 的那种只是不是语言的一部分(至少从 TS3.7 开始)。

所以,这就是我们必须停下来的地方。您可以展开循环并调用cb(cb(cb(...,或者调用reduce() 并使用类型断言。我认为类型断言真的不是那么糟糕。它专门用于您比编译器更聪明的情况......而这似乎是其中之一。

好的,希望对您有所帮助;祝你好运!

Link to code

【讨论】:

    【解决方案2】:

    我遇到了同样的事情,并且能够通过将acc 转换为所需的接口并将初始值转换为any 来使事情按需要工作:

    const result= ['a','b','c'].reduce((acc: IMyInterface,val)=>({...acc,[val]: 1}), {} as any);
    

    这应该将 result 设置为 IMyInterface 类型,而不会引发 tslint 错误。

    【讨论】:

    • 感谢您的回复。所以我正在寻找不需要铸造的东西。这包括投射到任何
    • 有趣的是。这实际上是错误的,因为在第一次迭代时 acc 是空的,我们说的是 IMyInterface 类型,它实际上不是 Partial
    • 是的,从技术上讲,reducer 内部是错误的,但是当在这些特定情况下使用时,它已经完成了工作,我正在使用一个非常简单、简洁的 reducer 转换已知数量。由于函数很短,我已经放弃寻找更“正确”的解决方案——我只关心最终结果是否正确输入。不过,在更复杂的情况下,我同意在函数中正确输入会很棒。
    猜你喜欢
    • 1970-01-01
    • 2019-07-25
    • 2012-01-11
    • 1970-01-01
    • 2014-10-15
    • 2017-02-26
    • 2011-02-27
    • 1970-01-01
    相关资源
    最近更新 更多