强制对象文字中的数组只能包含外部对象的键

时间:2018-10-16 20:46:42

标签: typescript keyof

我有以下接口定义。

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

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

我希望“儿童”属性仅接受已定义组件的键。 对于“ root.children”属性,它只能接受root,button1和button2:

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

但是它也接受任意字符串,例如示例“ button3 ”。

2 个答案:

答案 0 :(得分:0)

  

但是它也接受任意字符串,例如示例“ button3”。

原因:

您有

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

因此keyof IComponents解析为'root' | string或有效地string。您几乎总是从不希望拥有定义明确的名称和string索引器in the same group

解决方案

我会重新考虑非循环设计。以下:

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

list的类型取决于分配的对象。理想情况下,您将找出某种类型的方法来强制执行 可以分配的内容。

答案 1 :(得分:0)

您无法定义单个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'
  },
});