如何正确推断索引签名?

时间:2019-08-12 12:19:34

标签: typescript typescript-generics

似乎将输入arg推导为通用索引签名并不能按预期工作(或者我完全缺少missing)。

如何使返回类型被推断并正确验证输入?

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,但这似乎是有效的输入

1 个答案:

答案 0 :(得分:1)

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

通常,在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