簡體   English   中英

TypeScript 映射類型,具有元組/聯合支持的深度轉換

[英]TypeScript mapped type that deep transforms with tuple/union support

我正在嘗試創建一個實現遞歸類型轉換的通用映射類型。

非常感謝@jcalz 提供了來自https://stackoverflow.com/a/60437613/1401634的優雅解決方案。

(請注意,該票與不同的 scope,並且與此票不重復)

如下圖,當前映射類型不支持元組或聯合類型。

有沒有辦法支持聯合類型並使規范通過?

游樂場准備游樂場鏈接

/**
 * Recursive type transformation. Support scalar, object, array, and tuple within original type.
 * @example
 * DeepReplace<Original, [From, To] | [Date, string] | ...>
 */
type DeepReplace<T, M extends [any, any]> = T extends M[0] ?
  Replacement<M, T>
  :
  {
    [P in keyof T]: T[P] extends M[0]
    ? Replacement<M, T[P]>
    : T[P] extends object
    ? DeepReplace<T[P], M>
    : T[P];
  }

type Replacement<M extends [any, any], T> =
  M extends any ? [T] extends [M[0]] ? M[1] : never : never;

// Tests

const obj = {
  number: 1,
  date: new Date(),
  deep: { date: new Date() },
  arrayDeep: [{ date: new Date() }],
  array: [new Date()],
  tuple: [new Date(), 2, true],
  tupleWithObj: [{ date: new Date() }, 2, 'hi', { hello: 'world' }],
  tupleWithTuple: [[1, false], [2, new Date()], [3, { date: new Date() }]]
}

type ArrayType<A extends unknown[]> = $ElementType<A, number>

const date = new Date()
const number = 2
const n = null
const nestedArray = [[[new Date()]]]

const scalarTest: DeepReplace<typeof date, [Date, string]> = 'string' // ✅
const constTest: DeepReplace<typeof number, [Date, string]> = 2 // ✅
const primitiveTest: DeepReplace<typeof n, [Date, string]> = null // ✅
const nestedArrayTest: DeepReplace<typeof nestedArray, [Date, string]> = [[['string']]] // ✅

let o: DeepReplace<typeof obj, [Date, string]>

const innocentTest: typeof o.number = 2 // ✅
const shallowTest: typeof o.date = 'string' // ✅
const deepTest: typeof o.deep.date = 'string' // ✅
const arrayTest: ArrayType<typeof o.array> = 'string' // ✅
const arrayObjTest: ArrayType<typeof o.arrayDeep>['date'] = 'string' // ✅
const tupleTest: typeof o.tuple = ['string'] // ❌ Type 'string' is not assignable to type 'number | boolean | Date'.
const tupleObjTest: typeof o.tupleWithObj = { date: 'string' } // ❌ Object literal may only specify known properties, and 'date' does not exist in type '(string | number | { date: Date; soHard?: undefined; } | { soHard: string; date?: undefined; })[]'
const tupleTupleTest: typeof o.tupleWithTuple = [[1, false], [2, 'string'], [3, { date: 'string' }]] // ❌ Type 'string' is not assignable to type 'number | boolean | Date | { date: Date; }'; Type 'string' is not assignable to type 'Date'.

有兩個部分(讓它們工作需要兩件事)

聯合類型

您需要使用ExtractExclude Utility 類型

元組類型

您需要使用infer關鍵字

放在一起:

/**
 * Recursive type transformation. Support scalar, object, array, and tuple as original type.
 * @example
 * DeepReplace<Original, [From, To] | [Date, string] | ...>
 */
type DeepReplace<T, M extends [any, any]> = T extends M[0] ?
  Replacement<M, T>
  :
  {
    [P in keyof T]: T[P] extends M[0]
    ? Replacement<M, T[P]>
    : T[P] extends (infer R)[] // Is this a Tuple or array
    ? DeepReplace<R, M>[] // Replace the type of the tuple/array
    : T[P] extends object
    ? DeepReplace<T[P], M>
    : Extract<T[P], M[0]> extends M[0] // Is this a union with the searched for type?
    ? UnionReplacement<M, T[P]> // Replace the union
    : T[P];
  }

type Replacement<M extends [any, any], T> =
  M extends any ? [T] extends [M[0]] ? M[1] : never : never;

type UnionReplacement<M extends [any, any], T> =
  DeepReplace<Extract<T, object>, M> // Replace all object types of the union
  | Exclude<T, M[0] | object> // Get all types that are not objects (handled above) or M[0] (handled below)
  | M[1]; // Direct Replacement of M[0]

操場

同樣對於閱讀本文以轉換對象的任何人,您仍然需要真正轉換它們,這只會更改 typescript 的類型,並且不保證您將獲得正確的 object,您仍然需要進行轉換 JS 樣式

DeepAddUnion

感謝 Elias Schablowski 的出色回答

在搜索類型以深入擴展匹配類型時,我偶然發現了這個問題和導致它的上一個問題。 我能夠根據 Elias 示例的整體結構想出一些效果很好的東西,而不是替換一個類型,只是通過將它與另一個匹配類型聯合來擴展它。 也許這里的其他人會發現它很有用。

type DeepAddUnion<T, M extends [unknown, unknown]> = T extends M[0]
  ? UnionWithMatchingTuplePartner<M, T>
  : {
      [P in keyof T]: T[P] extends M[0]
        ? UnionWithMatchingTuplePartner<M, T[P]>
        : T[P] extends (infer R)[] // Is this a Tuple or array
        ? DeepAddUnion<R, M>[] // Handle the type of the tuple/array
        : T[P] extends object
        ? DeepAddUnion<T[P], M>
        : Extract<T[P], M[0]> extends M[0] // Is this a union with the searched for type?
        ? AddUnionToUnionedTypes<M, T[P]> // Add to the union
        : T[P];
    };

type UnionWithMatchingTuplePartner<
  M extends [unknown, unknown],
  T
> = M extends unknown ? ([T] extends [M[0]] ? M[0] | M[1] : never) : never;

type AddUnionToUnionedTypes<M extends [unknown, unknown], T> =
  | DeepAddUnion<Extract<T, object>, M> // Handle all object types of the union
  | Exclude<T, M[0] | object> // Keep all types that are not objects
  | M[0] // Keep original type
  | M[1]; // Add the matching tuple value

type AcceptEquivalents<T> = DeepAddUnion<
  T,
  [undefined, null] | [object, Prisma.JsonValue]
>;

不過還有兩件事我不太明白

(我將使用給出的原始解決方案中的變量)

  1. 以下代碼 [T] 擴展了Replacement中的[T] extends [M[0]] 為什么需要將TM[0]包裝在數組中(假設就是這樣)?
  2. 我覺得我對Replacement中的通用類型M的理解存在差距。 直覺上寫的東西是有道理的,但我知道M實際上是許多元組的並集,我覺得我不太明白M[1]是如何與正確的M[0]匹配的,或者我不知道了解擴展聯合類型如何能夠縮小聯合類型? ♂️

很抱歉從答案中提出后續問題,我還差 14 位代表無法發表評論

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM