[英]Typescript mapped type from Array
舉個例子 function:
// Merges objects | arrays
function merge(...values) {
return Object.assign(
{},
...values.map((value) =>
Array.isArray(value)
? Object.fromEntries(value.map((val) => [val, null]))
: value,
),
)
}
merge({k1: 1}, {k2: 2}) // {k1: 1, k2: 2} - 👌
merge({k1: 1}, ['k2']) // {k1: 1, k2: null} - 👌
我試圖弄清楚如何為 function 編寫類型並保持結果的結構
// Types definition
export type MixType<T> = T extends string[]
? { [K in T[number]]: null }
: { [K in Extract<keyof T, string>]: T[K] }
type Test1 = MixType<{k1: 1}> // Type is: {k1: 1} - 👌
type Test2 = MixType<['k1']> // Type is: {k1: null} - 👌
// Bind types into the function
function merge<V1>(v: V1): MixType<V1>
function merge<V1, V2>(v1: V1, v2: V2): MixType<V1> & MixType<V2>
function merge(...values) { // ... }
const t1 = merge({k1: 1}, {k2: 2}) // typeof t1: {k1: number} & {k2: number} - 👌
const t2 = merge({k1: 1}, ['k2']) // typeof t2: {k1: number} & {[x:string]: null} - 🤷♂️
const t3 = merge(['k1']) // typeof t3: {[x: string]: null} - 🤷♂️
如何使 typescript 與 arrays 保持結果結構? 我如何理解T[number]
和Extract<keyof T, string>
都產生一個聯合。 所以在這兩種情況下它必須是相同的{[K in <Union>}
。 但是對於 arrays ts 會丟棄結果結構。
於是有疑問:
merge({k1: 1}, ['k2'])
獲得{k1: number} & {k2: null}
的類型merge({k1: 1}, ['k2'])
以獲得{k1: 1} & {k2: null}
的類型綜合回答
基於@TadhgMcDonald-Jensen 的回復和來自@TitianCernicova-Dragomir 的評論
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (
k: infer I,
) => void
? I
: never
type MixType<T> = T extends readonly string[]
? { [K in T[number]]: null }
: { [K in keyof T]: T[K] }
function merge<
Vs extends Array<S[] | Record<S, V>>,
S extends string,
V extends string | number | boolean | object,
>(...values: Vs): UnionToIntersection<MixType<Vs[number]>> {
return Object.assign(
{},
...values.map((value) =>
Array.isArray(value)
? Object.fromEntries(value.map((val) => [val, null]))
: value,
),
)
}
const t1 = merge({ k1: 1 }, { k2: '2' })
// typeof t1: { k1: 1} & {k2: '2'} - 👍
const t2 = merge({ k1: true }, ['k2'])
// typeof t2: { k2: null} & {k1: true} - 👍
Typescript 錯誤在於不將字符串文字作為泛型類型,除非它是直接泛型: playground
function takeString<T extends string>(a:T): [T,T] {return [a,a]}
function takeAny<T>(a:T): [T,T] {return [a,a]}
function takeListOfStr<L extends string[]>(a:L): L {return a}
const typedAsSpecificallyHello = takeString("hello")
// typed as ["hello", "hello"]
const typedAsString = takeAny("hello")
// typed as [string, string]
const evenWorse = takeListOfStr(["hello", "hello"])
// typed just as string[]
這是有道理的,如果字符串列表出現在某處,可以合理地假設您放在那里的特定文字實際上並不重要,它只是一個字符串列表。 然而as const
完全覆蓋了這種行為: playground
function readsListOfStringsWithoutModifying<T extends readonly string[]>(a:T){return a}
const tt = readsListOfStringsWithoutModifying(["a", "a"] as const)
由於您的 function 確實保證傳遞的數據未被修改,因此您不會破壞任何打字稿內部結構,並且設置您的 generics 以接受只讀數組並不難。 所以你會想做這樣的事情: 游樂場
type UnionToIntersection<U> = // stolen from https://stackoverflow.com/questions/50374908/transform-union-type-to-intersection-type
(U extends any ? (k: U)=>void : never) extends ((k: infer I)=>void) ? I : never
type VALID_ARG = {[k:string]:unknown} | (readonly string[])
// Types definition
export type MixType<T extends VALID_ARG> = T extends readonly string[]
? Record<T[number], null>
// here we are removing any readonly labels since we are creating a new object that is mutable
// you could also just use `T` on this line if you are fine with readonly sticking around.
: {-readonly [K in keyof T]: T[K] }
// Bind types into the function
function merge<Vs extends VALID_ARG[]>(...values:Vs): UnionToIntersection<MixType<Vs[number]>> {
return Object.assign({}, ...values.map(
(value) => Array.isArray(value)
? Object.fromEntries(value.map((val) => [val, null]))
: value,
))
}
const t1 = merge({k1: 1}, {k2: 2})
// this no longer keeps 1,2, just stays `number`
const t2 = merge({k1: 1} as const, ['k2'] as const)
// but adding `as const` makes everything retained
這里發生了一些事情,首先是泛型被限制為readonly string[]
或帶有字符串鍵的 object,這簡化了您之前的一些過濾邏輯,其次 function 將這些對象的列表作為泛型並將Vs[number]
傳遞給MixType
,這得到所有 arguments 的並集傳遞給條件類型,返回部分 object 類型的並集,然后使用(有人 hacky) UnionToIntersection
我們得到Vs[number]
產生的原始並集Vs[number]
來表示所有部分對象的交集。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.