Отображаемый тип TypeScript, который глубоко трансформируется с поддержкой кортежей / объединений - PullRequest
0 голосов
/ 06 августа 2020

Я пытаюсь создать универсальный сопоставленный тип, который обеспечивает преобразование рекурсивного типа.

Огромное спасибо @jcalz за элегантное решение из { ссылка }.

(Обратите внимание, что билет был с другой областью действия и не дублируется с этим билетом)

Как показано ниже, текущий сопоставленный тип не поддерживает кортеж или тип объединения.

Есть ли способ для поддержки профильных типов и соответствия спецификациям?

Готовая площадка Ссылка на площадку

/**
 * 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'.

1 Ответ

1 голос
/ 06 августа 2020

Есть две части (и две вещи, необходимые для их работы)

Типы объединения

Вам нужно будет использовать Extract и Exclude Типы утилит

Типы кортежей

Вам необходимо использовать ключевое слово 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]

Площадка

Также для всех, кто читает это для преобразования объектов, вам все равно нужно действительно преобразовать их, это просто меняет тип машинописного текста и не гарантия, что вы получите нужный объект, ВАМ ЕЩЕ НЕОБХОДИМО ПРЕОБРАЗОВАТЬ JS СТИЛЬ

...