Обеспечение того, чтобы массив внутри литерала объекта мог содержать только ключи внешнего объекта - PullRequest
0 голосов
/ 16 октября 2018

У меня есть следующие определения интерфейса.

interface IComponents {
  root: IComponent,
  [key: string]: IComponent,
}

interface IComponent {
  type: string,
  children?: Array<keyof IComponents>;
}

Я хочу, чтобы свойства "children" принимали только ключи определенных компонентов.в случае свойства "root.children" он должен принимать только root, button1 и button2:

const list: IComponents = {
  root: {
    type: 'panel',
    children: ['button1', 'button2', 'button3']
  },
  button1: {
    type: 'button'
  },
  button2: {
    type: 'button'
  },
}

Но он также принимает произвольные строки, как в примере " button3 ».

Ответы [ 2 ]

0 голосов
/ 17 октября 2018

Нет единого типа IComponents, который вы можете определить, который включает все (и только) списки компонентов, которые внутренне согласованы в том смысле, что списки children относятся только к определенным компонентам;для этого потребуется форма экзистенциальных типов.Однако вы можете определить универсальный тип IComponents<K>, который представляет допустимый список компонентов с определенным списком ключей K, и это позволит вам определить функции, которые являются универсальными в параметре типа K и принять IComponents<K>и, таким образом, может быть вызван в любом допустимом списке компонентов.Например:

type IComponents<K extends string> = {
  [P in K]: IComponent<K>;
} & {
  // Needed for contextual typing to work.
  // https://github.com/Microsoft/TypeScript/pull/27586 might remove the need for this.
  [n: string]: IComponent<K>
};

interface IComponent<K extends string> {
  type: string,
  children?: Array<K>;
}

function processComponents<K extends string>(arg: IComponents<K>) {
  // ...
}

// OK
processComponents({
  root: {
    type: 'panel',
    children: ['button1', 'button2']
  },
  button1: {
    type: 'button'
  },
  button2: {
    type: 'button'
  },
});

// Error (unfortunately it doesn't pinpoint the mistake)
processComponents({
  root: {
    type: 'panel',
    children: ['button1', 'button2', 'button3']
  },
  button1: {
    type: 'button'
  },
  button2: {
    type: 'button'
  },
});
0 голосов
/ 17 октября 2018

Но он также принимает произвольные строки, как в примере "button3".

Причина:

У вас есть

interface IComponents {
  root: IComponent,
  [key: string]: IComponent,
}

, поэтомуkeyof IComponents разрешается до 'root' | string или эффективно string.Вы почти всегда никогда не хотите иметь четко определенные имена и string индексаторы в одной группе .

Решение

Я бы пересмотрел нециклический дизайн.Следующее:

const list: IComponents = {
  root: {
    type: 'panel',
    children: ['button1', 'button2', 'button3']
  },
  button1: {
    type: 'button'
  },
  button2: {
    type: 'button'
  },
}

Тип list зависит от назначенного объекта.В идеале вы должны выяснить, каким образом тип обеспечивает то, что может быть назначено.

...