打字稿:从对象数组派生联合类型

Typescript: derive union type from array of objects

我想声明一个类型强制的项目数组,并能够从中派生联合类型。如果您没有显式地为数组中的项目指定类型,则此模式有效。我不确定如何最好地解释它,所以这里有一个例子:

示例 1

type Pair = {
  key: string;
  value: number;
};

const pairs: ReadonlyArray<Pair> = [
  { key: 'foo', value: 1 },
  { key: 'bar', value: 2 },
] as const;

type Keys = typeof pairs[number]['key']

示例 2

type Data = {
  name: string;
  age: number;
};

const DataRecord: Record<string, Data> = {
  foo: { name: 'Mark', age: 35 },
  bar: { name: 'Jeff', age: 56 },
} as const;

type Keys = keyof typeof DataRecord;

下面是使用 as const 时派生密钥的示例。我想要同样的行为,但数组被显式键入。

const pairs = [
  { key: 'foo', value: 1 },
  { key: 'bar', value: 2 },
] as const;

type Keys = typeof pairs[number]['key']; // "foo" | "bar"

键的期望值:"foo"|"bar"

键的实际值:string

对于变量,您可以让编译器从初始化中推断出类型,也可以将其显式写出。如果您显式地编写它,那么会根据注释检查初始化值,但初始化程序的实际类型不会影响变量的类型(因此您会丢失所需的类型信息)。如果你让编译器推断它,就不再可能约束类型以符合特定接口(如你所愿)

这个问题的解决方案是使用通用函数来约束值并推断它的实际类型:

type Pair = {
  key: string;
  value: number;
};
function createPairsArray<T extends readonly Pair[] & Array<{key: V}>, V extends string>(...args: T) {
    return args
}

const pairs = createPairsArray(
  { key: 'foo', value: 1 },
  { key: 'bar', value: 2 },
)

type Keys1 = typeof pairs[number]['key']

type Data = {
  name: string;
  age: number;
};

function createDataObject<T extends Record<string, Data>>(arg: T) {
    return arg;
}
const DataRecord = createDataObject({
  foo: { name: 'Mark', age: 35 },
  bar: { name: 'Jeff', age: 56 },
})

type Keys2 = keyof typeof DataRecord;

Playground Link

注意:对于数组的情况,我们需要让编译器稍微强一点来推断 key 的字符串文字类型,因此整个 & Array<{key: V}>,其中 V 是一种类型参数扩展 string

通常的做法是:

  • 让 TS 通过省略显式类型 ReadonlyArray<Pair> 来推断 pairs 的类型(参见
  • Pair 中给 key 类型 "foo"|"bar"

如果你不想这样做,那么推断你的键的唯一方法是限制pairs 是使用辅助函数。 Pair 类型也将被通用化以保存给定的 key 字符串文字类型。您可以使用 IIFE 使分配紧凑:

type Pair<K = string> = {
    key: K;
    value: number;
};

const pairs = (<T>(p: readonly Pair<T>[]) => p)([
    { key: 'foo', value: 1 },
    { key: 'bar', value: 2 },
] as const) // readonly Pair<"foo" | "bar">[]

type Keys = typeof pairs[number]['key'] // "foo" | "bar"

Playground