我怎样才能摆脱部分<T>到 T 而不在 Typescript 中强制转换 [英] How can i move away from a Partial<T> to T without casting in 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
有两个参数,acc
和 val
.acc
参数属于泛型类型 T
,它必须可分配给 Partial
和 val
参数属于通用类型 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(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) &...
,用于泛型参数A
、B
、C
、D
等,然后希望编译器可以从您对 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!
这篇关于我怎样才能摆脱部分<T>到 T 而不在 Typescript 中强制转换的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!