一般打字稿问题说我迭代一个我知道其内容的数组,并应用一个归约来获取一个我确实知道类型的对象,例如:
interface IMyInterface {
a: number;
b: number;
c: number;
}
const result: IMyInterface = ['a','b','c'].reduce((acc: Partial<IMyInterface>,val)=>({...acc,[val]: 1}), {});
Run Code Online (Sandbox Code Playgroud)
现在这不起作用,因为结果预计是Partial<IMyInterface>有意义的,考虑到 TS 无法告诉数组的内容将产生“完整”对象。但是,我需要做什么才能使结果为 IMyInterface 类型而不需要as IMyInterface?
这是一个 repl https://repl.it/@Sudakatux/KaleidooscopyGraciousApplicationpackage
提前致谢
这里的简短答案是:您非常需要使用类型断言,因为编译器不可能知道您正在做的事情是安全的。
更长的答案:为了让编译器知道发生了什么,您需要回调是通用的。这是一种输入方法:
const cb = <K extends keyof IMyInterface, T extends Partial<IMyInterface>>(
acc: T, val: K): T & Record<K, number> => ({ ...acc, [val]: 1 })
Run Code Online (Sandbox Code Playgroud)
该类型签名表明 需要cb两个参数acc和val。该acc参数是T必须可分配给 的泛型类型Partial<IMyInterface>,并且该val参数是K必须可分配给 的泛型类型keyof IMyInterface。那么回调的输出是T & Record<K, number>:也就是说,它是一个具有来自 的所有键和值的对象T,但它number在键 处也有一个确定的值K。因此,当您调用 时cb(),返回值的类型可能与 的类型不同acc。
这为编译器提供了足够的信息,使您可以避免类型断言...但前提是您手动执行类似reduce()操作cb(),通过将循环展开为一堆嵌套调用:
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
Run Code Online (Sandbox Code Playgroud)
在这里你可以看到编译器真的在照顾你,因为如果你cb()以错误的方式调用,你会得到一个错误告诉你。
不幸的是,的类型签名Array<T>.reduce(),
reduce<U>(
callbackfn: (previousValue: U, currentValue: T, currentIndex: number, array: readonly T[]) => U,
initialValue: U
): U;
Run Code Online (Sandbox Code Playgroud)
不足以表示每次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()并使用类型断言。我认为类型断言实际上并没有那么糟糕;它专门针对您比编译器更聪明的情况......而这似乎就是其中之一。
好的,希望有帮助;祝你好运!