如何正确推断索引签名?

How to infer index signatures correctly?

似乎无法将输入 arg 推断为通用索引签名(或者我完全遗漏了一些东西)。

如何推断 return 类型并正确验证输入?

interface Styles {
  contentAlign?: string;
  zIndex?: number;
}

function createTheme<S extends { [key: string]: Styles }>(theme: S) {
  return theme;
}

// this works, foo is marked as invalid
const style: Styles = {
  zIndex: 1,
  foo: 'bar', // <-- invalid
};

// once I try to use the Styles as index signature it allows other properties
const t = createTheme({
  Button: {
    zIndex: 1,
    foo: 'bar', // <-- valid??
  },
});

我希望 Type { foo: "bar" } is not assignable to type Styles,但它似乎是一个有效的输入

S extends { [key: string]: Styles } 意味着 S 可以是 { [key: string]: Styles } 的子类型。但这也意味着 S 的任何 属性 也可以是 Styles 的子类型,因此这意味着任何给定的键实际上可以具有比 [=15= 中指定的属性更多的属性].

通常在 OOP 中,允许在需要基类型的地方分配子类型,Typescript 仅在对象文字直接分配给特定类型时执行额外的 属性 检查。分配给泛型类型参数时,编译器不会执行过多的 属性 检查,因为它假定您要允许子类型(毕竟 S extends {...} 读取任何类型 S 扩展 {...}).

在你的情况下,因为你想允许任何键,但你实际上不想禁用对 Styles 的多余 属性 检查,我会使用对象的键作为类型参数而不是整个对象:

interface Styles {
  contentAlign?: string;
  zIndex?: number;
}

function createTheme<K extends PropertyKey>(theme: Record<K, Styles>) {
  return theme;
}

// this works, foo is marked as invalid
const style: Styles = {
  zIndex: 1,
  foo: 'bar', // <-- invalid
};

// once I try to use the Styles as index signature it allows other properties
const t = createTheme({
  Button: {
    zIndex: 1,
    foo: 'bar', // <-- error
  },
  Header: {
    zIndex: 1,
    foo: 'bar', // <-- error
  },
});

Play