Enforcing that an array within an object literal can only contain keys of the outer object

淺唱寂寞╮ 提交于 2019-12-25 00:48:58

问题


I have the following Interface definitions.

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

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

I want that the "children" properties accept only keys of defined Components. in the case of the "root.children"-property it should only accept root, button1 and button2:

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

But it accepts also arbitrary strings, like in the example "button3".


回答1:


But it accepts also arbitrary strings, like in the example "button3".

Reason:

You have

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

so keyof IComponents resolves to 'root' | string or effectively string. You almost always never want to have well defined names and string indexers in the same group.

Solution

I would reconsider a non-cyclic design. The following:

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

The type of list depends on the assigned object. Ideally you would figure out some way that type enforces what can be assigned.




回答2:


There's no single IComponents type you can define that includes all (and only) component lists that are internally consistent in the sense that the children lists only refer to defined components; this would require a form of existential types. However, you can define a generic type IComponents<K> that represents a valid component list with a specific key list K, and this will allow you to define functions that are generic in a type parameter K and accept an IComponents<K> and thus can be called on any valid component list. For example:

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'
  },
});


来源:https://stackoverflow.com/questions/52843813/enforcing-that-an-array-within-an-object-literal-can-only-contain-keys-of-the-ou

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!