Как получить форму объекта, а затем изменить тип каждого свойства листа? - PullRequest
2 голосов
/ 21 апреля 2020

Например, возьмем этот интерфейс:

interface SomeObject {
  prop1: number;
  prop2: string;
  prop3: {
    innerProp1: number[];
    innerProp2: string[];
    innerProp3: {
      deeperProp1: string[];
      deeperprop2: boolean;
    },
    innerProp4: {
      [key: string]: any;
    },
    innerProp5: {
      [key: string]: any;
    }
  }
}

Я хочу создать тип, который принимает форму любого объекта, затем возвращает ту же форму объекта, но с предоставленным типом для свойств «листа» и каждое свойство объекта может быть необязательным. Что-то похожее на приведенное ниже:

type ModifyShapeType<Shape, NewType> = ???

Так, что при использовании, например, с интерфейсом выше, я получу безопасность типов для той же формы объекта, но с предоставленным типом:

const myObject: ModifyShapeType<SomeObject, boolean> = {
  prop1: true;
  prop2: true;
  prop3: {
    // innerProp1: true;
    // innerProp2: false;
    innerProp3: {
      deeperProp1: true;
      // deeperprop2: true;
    },
    innerProp4: true,
    // innerProp5: false
  }
};

Я придумал тот, что ниже, но я хочу избавиться от исходных типов из фигуры и заменить его тем, что я хочу, и, если возможно, сохранить специфичность при чтении и записи свойств.

type ModifyShapeType<S, T> = Partial<Record<keyof S, Partial<S[keyof S] | T>>>;

Вот TypeScript Playground .

Предостережения в настоящее время:

  1. Типы, все еще выводимые из исходного типа объекта, фактически это теперь все mixed.
  2. Все свойства теперь имеют один и тот же тип (специфичность при потере чтения), что также означает небезопасные записи (специфичность при потере записи)

Возможно ли это?

1 Ответ

1 голос
/ 21 апреля 2020

Звучит так, как будто вам нужен сопоставленный рекурсивный тип.

Чтобы создать его, вы должны выполнить итерацию по каждому ключу и посмотреть, является ли он объектом (ветвью) или другим значением (листом) , Если это ветка, то рекурсивно. Если это лист, выведите желаемый тип значения. Проблема определения того, что определяет лист, немного сложна, и спецификация приложения c (после того, как каждое значение в javascript имеет свойства и может быть несколько объектно-подобным).

Так что вам понадобится условный тип брах-детектора и рекурсивный сопоставленный тип.

// Returns T if the T is a branch. Otherwise it returns `never`.
type IsBranch<T> = 
  // Is T an object?
  T extends { [k: string]: any }

    // T is an object. Is it also an array?
    ? T extends any[]

      // T is an object, but also is an array. This is a leaf.
      ? never

      // T is an object, but is not also an array. This is a branch.
      : T

    // T is not an object. This is a leaf.
    : never

// Recursively process each key.
// If it is a branch, process its keys and return the Partial of that branch.
// If it is a leaf, replace with the value type.
type ModifyShapeType<S, T> = S extends IsBranch<S> ?
  Partial<{ [k in keyof S]: ModifyShapeType<S[k], T> }> :
  T

const a: ModifyShapeType<{ a: number }, boolean> = { a: true }
const b: ModifyShapeType<{ a: number[] }, boolean> = { a: true }
const c: ModifyShapeType<{ a: { b: number } }, boolean> = { a: { b: true } }

игровая площадка

Одна хитрость заключается в том, что массив type выглядит как массив, поэтому для этого нам нужен специальный случай.

Еще одна сложная задача - попытаться получить разницу между { a: number } и { [k: string]: number }. Кажется, вы хотите рассматривать первое как ветвь, а второе как лист. И я не уверен, что есть способ сделать это. Можно провести условный тест T[string], чтобы проверить, индексируем ли он, но я не совсем понял, что это.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...