我怎样才能摆脱部分<T>到 T 而不在 Typescript 中强制转换 [英] How can i move away from a Partial<T> to T without casting in Typescript

查看:21
本文介绍了我怎样才能摆脱部分<T>到 T 而不在 Typescript 中强制转换的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

一般打字稿问题说我遍历一个我知道其内容的数组并应用一个reduce来取回一个我知道类型的对象例如:

interface IMyInterface {一个号码;b:数量;c:数量;}const 结果:IMyInterface = ['a','b','c'].reduce((acc: Partial,val)=>({...acc,[val]: 1}), {});

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

这是一个副本 https://repl.it/@Sudakatux/KaleidoscopicGraciousApplicationpackage

提前致谢

解决方案

这里的简短回答是:您几乎需要使用 类型断言,因为不可能让编译器知道你在做什么是安全的.

<小时>

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

const cb = >(acc: T, val: K): T &记录<K,数字>=>({ ...acc, [val]: 1 })

该类型签名表示 cb 有两个参数,accval.acc 参数属于泛型类型 T,它必须可分配给 Partialval 参数属于通用类型 K,它必须可分配给 keyof IMyInterface.然后回调的输出是 T &Record<K, number>:也就是说,它是一个包含来自T的所有键和值的对象,但它也有一个确定的number值在键 K.因此,当您调用 cb() 时,返回值的类型可能与 acc 的类型不同.

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

const 结果:IMyInterface = cb(cb(cb({}, "a"), "b"), "c");//好的const 仍然可以:IMyInterface = cb(cb(cb({}, "a"), "c"), "b");//好的const 错误:IMyInterface = cb(cb(cb({}, "b"), "b"), "c");//错误!缺少属性a"

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

不幸的是,类型签名对于 Array.reduce(),

reduce(callbackfn: (previousValue: U, currentValue: T, currentIndex: number, array: readonly T[]) =>你,初始值:U):U;

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

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

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

链接到代码

General Typescript question say I iterate over an array which i know its content and apply a reduce to get an object back which I do know the type for instance:

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

Now that wont work because result is expected to be Partial<IMyInterface> which makes sense, considereing TS cant tell the content of the array will produce the "full" object. However What do I need to do so that result can be of type IMyInterface without the need of as IMyInterface ?

Here is a repl https://repl.it/@Sudakatux/KaleidoscopicGraciousApplicationpackage

Thanks in advance

解决方案

The short answer here is: you pretty much need to use a type assertion because it's not possible to have the compiler figure out that what you're doing is safe.


The much longer answer: in order to even begin to let the compiler know what's going on, you need the callback to be generic. Here's one way to type it:

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

That type signature says that the cb takes two parameters, acc and val. The acc parameter is of generic type T which must be assignable to Partial<IMyInterface>, and the val parameter is of generic type K which must be assignable to keyof IMyInterface. Then the output of the callback is T & Record<K, number>: that is, it is an object with all the keys and values from T, but it also has a definite number value at the key K. So when you call cb(), the return value is potentially of a different type from that of acc.

This gives enough information to the compiler to allow you to avoid type assertions... but only if you perform the reduce()-like operation with cb() manually, by unrolling the loop into a bunch of nested calls:

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

Here you can see that the compiler is really looking out for you, since if you call cb() in the wrong way, you get an error telling you so.

Unfortunately, the type signature for Array<T>.reduce(),

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

is insufficient to represent the successive type narrowing that happens each time callbackfn is called on elements of the array. And as far as I can tell, there's no way to alter it to do this. You want to say that the callbackfn type is some crazy intersection of types corresponding to how it behaves for each successive member of the array, like ((p: A, c: this[0])=>B) & ((p: B, c: this[1])=>C) & ((p: C, c: this[2])=>D) & ..., for generic parameters A, B, C, D, etc., and then hope that the compiler can infer these parameters from your call to reduce(). Well, it can't. The kind of higher order inference just isn't part of the language (at least as of TS3.7).

So, that's where we have to stop. Either you can unroll the loop and call cb(cb(cb(..., or you call reduce() and use a type assertion. I think the type assertion really isn't all that bad; it's meant specifically for situations in which you are smarter than the compiler... and this seems to be one of those times.

Okay, hope that helps; good luck!

Link to code

这篇关于我怎样才能摆脱部分&lt;T&gt;到 T 而不在 Typescript 中强制转换的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆