(TS版本--4.1)
我正在嘗試一個可以接受任何長度的陣列/元組的通用函式的型別;每個元素將有相同的屬性名稱,但可能的型別不同。
type Param<X = unknown, Y = unknown> = {
x?: X
y: Y
}
//回傳型別是一個固定的元組:對于每個元素,如果定義了就回傳x,否則回傳y。
function func(arg: Param[]/span>) {
return arg. map(span class="hljs-params">elem => 'x'/span> in elem ? elem.x : elem.y )
}
示例:
func([ {x: 'string', y: 123}。])
func([ {x: 'string', y: 123}, {x: 123, y: 'string' } ])
*update: 更新了函式,以澄清我為什么明確地關心X和Y:實際的回傳型別是一個元組,其各個元素可以是X或Y,這取決于原始引數
。我真正想得到的是讓編譯器推斷每個引數的長度和元素型別,從而使結果被相應地型別化。
我已經嘗試了一些東西
我已經嘗試了一些東西。
Param<X,Y>type InferX<T extends [...any[]] > = {
[K in keyof T] : T[K] extends Param<infer X, any> ? X : never
}
type InferY<T extends [...any[]] > = {
[K in keyof T] : T[K] extends Param<any, infer Y> ? Y : never
}
- 給函式一些型別引數,以包括嵌套的型別,并將函式引數表示為一個傳播運算式,映射到這些通用型別
//this works when I provide explicit type parameters, but won't infer them implicitly from the arguments. assuming everything is `unknown` i.e. func<Param< unknown, unknown> [] , unknown[] , unknown[]>
function func<
T extends Param[] 。
X extends { [K in keyof T] 。X[K] } = InferX<T> 。
Y extends { [K in keyof T] 。Y[K] } = InferY<T> 。
>(
arg: [...{ [K in keyof T]: Param<X[K], Y[K]> }] 。
) {
return arg. map(span class="hljs-params">elem => 'x'/span> in elem? elem.x : elem.y ) as [
...{ [K in keyof T] 。T[K] extends {x: X[K]}? ? X[K] : Y[K] }
]
}
//application 1: works ok[/span
func<[{x: string, y: number}, {x: number, y: string}] > ([{x: 'string', y: 123}, {x: 123, y: 'string'}] )
//application 2: doesn't work - types are unknown
func([{x: 'string', y: 123}] )
//當我試圖更明確地使用T型別時,它根本無法編譯--"其余元素型別必須是陣列型別。"在傳播引數型別上出現錯誤。
function func<
T extends { [K in keyof T] 。Param<X[K], Y[K]> } & any[],
X extends { [K in keyof T] 。X[K] } = InferX<T> 。
Y extends { [K in keyof T] 。Y[K] } = InferY<T> 。
>(
arg: [...{ [K in keyof T]: Param<X[K], Y[K]> }] // ERRORreturn arg. map(span class="hljs-params">elem => 'x'/span> in elem? elem.x : elem.y ) as [
...{ [K in keyof T] 。T[K] extends {x: X[K]}? ? X[K] : Y[K] }
]
}
想知道是否有人知道如何讓推理正確作業/我是否想錯了?
謝謝!
uj5u.com熱心網友回復:
為了獲得最好的結果,讓型別引數推理對編譯器來說盡可能簡單
當你呼叫一個泛型函式而沒有手動指定其型別引數時,編譯器需要推斷這些型別引數,通常是從作為引數傳遞給該函式的值中推斷出來的。假設我們有一個函式,它的呼叫簽名是
。// type F<T> = .../span>
宣告 function foo<T>(x: F<T>) 。void;
然后我們這樣呼叫:
// declare const someValue: .../span>
foo(someValue)。
然后編譯器的作業是根據someValue的型別和F<T>的定義來確定T。 編譯器必須嘗試反轉 F,并從F<T>推匯出T。 這一點越簡單,推斷出的型別引數就越有可能符合你的期望。
在你的案例中,你有類似于
的東西function func<
T extends Param[] 。
X extends { [K in keyof T] 。X[K] } = InferX<T> 。
Y extends { [K in keyof T] 。Y[K] } = InferY<T> 。
>(
arg: [...{ [K in keyof T]: Param<X[K], Y[K]> }] 。
): void。
但是編譯器無法從一個型別為[...{ [K in keyof T]>的值中推斷出。 對編譯器來說,這實在是太多的型別操作了。 如果你做一些更簡單的事情,你會有更好的運氣,例如T, X, 和Y。Param<X[K], Y[K]> }]
span class="hljs-keyword">function func< T extends Param[]> (arg: T): void;
函式引數型別中的變數元組型別給了編譯器一個提示,以推斷元組而不僅僅是陣列
這樣做是可行的,但是編譯器傾向于推斷陣列型別而不是元組型別:
declare function func< T extends Param[]> (arg: T): void;
func([{ x: ""/span>, y: 1 }, { x: 1, y: "" }])
// T推斷為陣列<{x: string, y: number}。| {x:數字,y:字串}>
由于你希望T被推斷為一個元組型別,而不僅僅是一個陣列型別,你可以使用變數元組型別來獲得這種行為,通過將arg: T改為arg: [...T]/code>或者arg: readonly [...T]/code>:
declare function func< T extends Param[]> (arg: [. ..T]): void;
func([{ x: ""/span>, y: 1 }, { x: 1, y: "" }])
//T推斷為[{ x: string; y: number;}, { x: number; y: string;}]
計算回傳型別:沒有注解,產生
unknown[]
現在有一個問題是如何處理輸出型別。 它不是void。 讓我們研究一下示例代碼的實作:
function func< T extends Param[]> (arg: [. ..T]) {
return arg。 map(span class="hljs-params">elem => 'x'/span> in elem ? elem.x : elem.y)
}
如果我們不注釋回傳型別,它將被編譯器確定為unknown[];陣列的map()方法并不保留元組的長度,而且它當然不能遵循高階邏輯,為不同的索引產生不同的型別。
所以你會得到unknown[]:
const p: Param<string, number> = (
[{ x: ""/span>, y: 1 }, { y: 1 }, { x: undefined, y: 1 }] 。
)[Math.floor(Math.random() * 3) ]
const result = func([
{ x: ""/span>, y: 1 },
{ y: 1 },
{ x: undefined, y: 1 },
p
])
// const result: unknown[]。
這是真的,但對你來說是無用的。 由于編譯器不能推斷出 計算回傳型別:每個元素的 好吧,對于每個數字索引 這就好了,我們有一個長度為4的元組,而且 一個缺點是,我們在使用的時候會出現一些問題。
一個缺點是編譯器沒有意識到,如果 另一個是,如果map()的回傳值的強型別,我們將需要將其斷言為T的函式。 但是什么T的函式?
x和y屬性的聯合I,你看元素T[I]并回傳其"x"屬性的型別T[I]["x"]或其"y"屬性的型別T[I]["y"]。 因此,第一種方法是回傳這些型別的union://這里E是T陣列的某個元素。
type XorY<E> = E extends Param ? E["x"] | E["y"] : never;
function func<T extends Param[]>(arg: [...T]){
return arg。 map(span class="hljs-params">elem => 'x'/span> in elem ? elem.x : elem.y) 如
{ [I in keyof T] 。XorY<T[I]> }
}
const result = func([
{ x: ""/span>, y: 1 },
{ y: 1 },
{ x: undefined, y: 1 },
p
])
// const result: [string | number, unknown, number | undefined, string | number | undefined]/span>
string和number型別大部分都在里面。 但是也有一些缺點。x存在,你肯定會得到它;估計對于result的第一個元素,型別應該是string,而不是string | number。
x是不知道存在的,型別unknown就會出來。 這實際上在技術上是正確的;編譯器不可能知道它推斷的{y: 1}意味著x屬性是明確缺失的。 {y: number}的型別并不意味著 "除了y之外沒有任何屬性存在";它只是意味著 "除了y之外沒有任何已知屬性存在"。 (也就是說,物件型別不是microsoft/TypeScript#12936中要求的那種 "精確"。) 所以編譯器決定T[I]['x']是unknown,這就破壞了事情。 讓我們做一件技術上不正確但又方便的事情,說一個像{y: 1}意味著x缺失,因此我們只想讓y的型別出來。
x如果它存在,y如果它不存在,如果我們不知道,就計算聯合體。
所以讓我們重新定義上面的XorY<E>型別,使它對陣列的元素型別E做以下分析:
- 如果
E有一個型別為X的非選擇的x屬性,我們應該回傳X。 - 如果
E沒有x屬性,但是它有一個y型別的Y屬性,我們應該回傳Y。 - 否則,如果
E有一個型別為X的可選x屬性和一個型別為Y的y屬性,那么我們應該回傳X | Y。
這應該涵蓋了所有的情況(當然也有可能是邊緣情況,所以你需要測驗)。
要準確地檢查x屬性是否是可選的有點困難;詳情請見本答案。 下面是一種寫XorY的方法:
type XorY<E> = E extends Param<any,infer Y> ? E['x'] extends infer X ?
'x' extends keyof E ? {} extends Pick<E, 'x'/span>> ? X | Y : X : Y
: 從來沒有 : 從來沒有
它以一種奇怪的方式計算X和Y作為E的函式;Y是通過條件型別推理找到的,但是對于X,我是直接索引到 E;這是因為如果你使用infer,X將不包括undefined,但可選屬性有時是undefined(給或給--exactOptionalProperties編譯器標志)。所以當涉及到undefined時,E['x']比infer更準確。
無論如何,用X和Y武裝起來,如果x是一個已知的鍵('x' extends keyof E),如果它是可選的({} extends Pick<E, 'x'>),我們回傳X | Y。 如果它是已知的但不是可選的,我們回傳X。 如果它是未知的,那么我們就回傳Y.
好吧,讓我們來試試:
const result = func( [
{ x: ""/span>, y: 1 },
{ y: 1 },
{ x: undefined, y: 1 },
p
])
// const result: [string, number, undefined, string | number | undefined]/span>
console.log(result) // [" ", 1, undefined, something]
完美! 這已經是我們所希望的最具體的了。 同樣,可能會有一些邊緣情況,你應該進行測驗以確保它在你的實際用例中發揮作用。 但我認為這是我能想象到的對所給示例代碼的最接近的解決方案。
uj5u.com熱心網友回復:
有一個替代版本:
type Elem = Param;
型別HandleFalsy<T extends Param> = (
T['x'] extends undefined] ?
? T['y'] ?
: (
未知 extends T['x'] ?
? T['y'/span>] : T['x'/span>]
)
)
Type ArrayMap<
Arr extends ReadonlyArray<Elem> 。
Result extends any[] = [] 。
> = Arr extends [] 。
? Result ?
: Arr extends readonly [infer H, ...infer Tail] ?
? Tail extends ReadonlyArray<Elem>
? H extends Elem> ?
? ArrayMap<Tail, [...Result, HandleFalsy< H> ]>
: 絕不
:永遠不會
: 從來沒有。
型別 Param<X = unknown, Y = unknown> = {
x?: X
y: Y
}
型別 Json =
| null =
|字串
| 數字
| 布爾型
| Array<JSON>
| {
[prop: string] 。Json
}
const tuple = <
X extends Json,
Y extends Json,
Tuple extends Param<X, Y> 。
Arr extends Tuple[] 。
>(data: [...Arr]/span>) =>
data.map(span class="hljs-params">elem => 'x'/span> in elem? elem.x : elem.y) as ArrayMap<Arr>
// [42, "only y"]
const result1 = tuple([{ x: 42, y: 'str' }, { y: 'only y' }])
// [42, "only y", "100"]/span>
const result2 = tuple([{ x: 42, y: 'str' }, { y: 'only y' }, { x: '100', y: 999999 }])
為了更好地理解ArrayMap和HandleFalsy中的內容,請看一下js表示法:
const HandleFalsy=(arg: Param)=> {
if (!arg.x) {
return arg.y y }
}
return arg.x }
}
const ArrayMap = (arr: ReadonlyArray<Elem>, result: any[] = [] )。) Array<Param[keyof Param]> => {
if (arr.length == 0) {
return result
}
const [head, ...tail] = arr;
return ArrayMap(tail, [...result, HandleFalsy(head)] )
如果你想了解更多關于函式引數推理的資訊,你可以閱讀我的文章
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/318615.html
標籤:
