[英]Correct type for nested array in a recursive function in typescript
我正在嘗試在打字稿中實現一個與 lodash 的_.flattenDeep(array)
非常相似的函數flattenDeep
,但是我遇到了一些類型定義問題,這是我的代碼:
export interface DeepArray<T> extends Array<T | DeepArray<T>> {}
export const flattenDeep = <T>(array: DeepArray<T>): T[] => {
return [].concat(...array.map(arr => {
if (Array.isArray(arr)) {
return flattenDeep(arr)
} else {
return arr
}
}))
}
flattenDeep([1, [2, [3, [4]], 5]]) // should return [1, 2, 3, 4, 5]
到目前為止它看起來不錯,當我測試它時,功能本身沒問題,但是flattenDeep()
函數推斷出錯誤的返回類型:
const arr = [1, [2, [3, [4]], 5]]
const flattenedArr = flattenDeep(arr)
// inferred type is:
// const flattenedArr: (number | (number | (number | number[])[])[])[]
// but it should expected to be number[] or Array<number>? since the final result is a one dimensional array
那么我如何解決這個問題以提供正確的類型?
問題在於,由於DeepArray<T>
是遞歸類型的並且在結構上與(T | DeepArray<T>)[]
,因此沒有對應於DeepArray<T>
的唯一T
例如,類型DeepArray<number>
與(number | DeepArray<number>)[]
兼容,因此與(number | (number | DeepArray<number>)[])[]
等(number | (number | DeepArray<number>)[])[]
。
因此,當編譯器試圖解釋一個型樣(number | number[])[]
如DeepArray<T>
對於一些T
,它不能保證推斷number
。 選號完全免費number | number[]
number | number[]
或number | (number | number[])[]
number | (number | number[])[]
,或者任何類型的東西,只要它為T
選擇的任何東西都允許(number | number[])[]
與DeepArray<T>
兼容。 因此,即使flattenDeep()
的實現確實壓平了值,您也不能指望“壓平” T
推理:
declare const flattenDeepOrig: <T>(array: DeepArray<T>) => T[]
const oops = flattenDeepOrig([1, [2, [3, [4]], 5]])
// const oops: (number | (number | (number | number[])[])[])[]
當然,您可以手動指定所需的T
值,編譯器將能夠檢查它是否有效,如果無效則生成錯誤:
const okayAnnotated = flattenDeepOrig<number>([1, [2, [3, [4]], 5]]) // number[]
但大概您希望編譯器為您解決這個問題。 為此,對於某些適當的FlattenArray<T>
定義,使用像<T>(array: T[])=>FlattenArray<T>[]
這樣的類型會更好,該定義將深度嵌套的數組類型顯式折疊為平面類型。 這是因為給定值[1, [2, [3, [4]], 5]]
如T[]
用於唯一合理推論候選T
是一樣的東西number | (number | (number | number[])[])[]
number | (number | (number | number[])[])[]
,此時您希望FlattenArray<T>
將其顯式展平為number
。 所以我們只需要定義FlattenArray<>
。
不幸的是, FlattenArray<T>
的直接定義FlattenArray<T>
編譯器不支持的方式循環:
type FlattenArrayOops<T> = T extends Array<any> ? FlattenArrayOops<T[number]> : T; // error!
// ~~~~~~~~~~~~~~~~ <-- Type alias 'FlattenArrayOops' circularly references itself.
有一個開放的建議, microsoft/TypeScript#26980 ,允許這種循環類型,但目前沒有官方支持的方法來獲得這種行為。
在這種情況下,我通常做的是通過手動將定義“展開”到一個可以達到某個固定深度的版本來近似圓形類型,如下所示:
type FlattenArray<T> = T extends Array<any> ? FA0<T[number]> : T;
type FA0<T> = T extends Array<any> ? FA1<T[number]> : T;
type FA1<T> = T extends Array<any> ? FA2<T[number]> : T;
type FA2<T> = T extends Array<any> ? FA3<T[number]> : T;
type FA3<T> = T extends Array<any> ? FA4<T[number]> : T;
type FA4<T> = T extends Array<any> ? FA5<T[number]> : T;
type FA5<T> = T extends Array<any> ? FA6<T[number]> : T;
type FA6<T> = T extends Array<any> ? FA7<T[number]> : T;
type FA7<T> = T extends Array<any> ? FA8<T[number]> : T;
type FA8<T> = T extends Array<any> ? FAX<T[number]> : T;
type FAX<T> = T; // bail out
在這里可以看到FlattenArray<T>
不是根據自身定義的,而是根據一個類似的類型FA0<T>
定義的,它是根據類似的類型FA1<T>
等定義的,直到達到深度FAX<T>
並退出。 只要您的用例往往不是非常深的嵌套,它就應該適合您:
declare const flattenDeep: <T>(array: T[]) => FlattenArray<T>[];
const okay = flattenDeep([1, [2, [3, [4]], 5]]) // number[]
好的,希望有幫助; 祝你好運!
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.