在TypeScript中定义复杂索引签名时,是否可以保持DRY

yc0p9oo0  于 2023-03-13  发布在  TypeScript
关注(0)|答案(2)|浏览(120)
export interface Type {
  name: string;
  extraField: number;
}

const keyValueMap: { [name: string]: Type } = {
  keyOne: {
    name: 'keyOne',
    extraField: 1,
  },
  keyTwo: {
    name: 'keyTwo',
    extraField: 2,
  },
};

假设我想使用索引签名定义一个键-值Map,有没有一种方法可以保持DRY并以某种方式避免复制键名?

vxf3dgd4

vxf3dgd41#

如果你要求保持DRY than,我不认为在子属性中复制map键名是个好主意,不可避免的是你会用父键名访问对象,所以你已经有了它。你可以从子属性中丢弃name属性。

**如果您坚持保留name属性,则可以利用泛型。**您可以使用下面的代码段,它将限制您只能添加与map属性键相同的键,并为属性名称启用IntelliSense。

type properties = "keyOne" | "keyTwo"

export interface Type<propertyKey> {
  name: propertyKey;
  extraField: number;
}

type data = {
  [propertyKey in properties]: Type<propertyKey>;
};

const keyValueMap: data = {
  keyOne: {
    name: 'keyOne',
    extraField: 1,
  },
  keyTwo: {
    name: 'keyTwo',
    extraField: 2,
  },
};
tjjdgumg

tjjdgumg2#

您可以创建一个helper函数,它将接受此类对象的数组并返回一个对象-该函数将为每个输入对象分配一个与其name属性值相对应的属性:
TSPlayground

type Mutable<T> = { -readonly [K in keyof T]: T[K] };

type NameMap<U extends { readonly name: string }> =
  { [T in U as T["name"]]: Mutable<T> };

type Item<
  Name extends string = string,
  Num extends number = number,
> = {
  name: Name;
  extraField: Num;
};

function createNameMap<Items extends ReadonlyArray<Readonly<Item>>>(
  items: Items,
): NameMap<Items[number]> {
  const map: any = {};
  for (const item of items) map[item["name"]] = item;
  return map;
}

const actual = createNameMap([
  { name: "keyOne", extraField: 1 },
  { name: "keyTwo", extraField: 2 },
] as const);
//^^^^^^^^
// Use a const assertion to infer literal property values

actual.keyOne.name
            //^? (property) name: "keyOne"

actual.keyOne.extraField
            //^? (property) extraField: 1

actual.keyTwo.name
            //^? (property) name: "keyTwo"

actual.keyTwo.extraField
            //^? (property) extraField: 2

const expected = {
  keyOne: { name: "keyOne", extraField: 1 },
  keyTwo: { name: "keyTwo", extraField: 2 },
};

const equalAtRuntime = JSON.stringify(actual) === JSON.stringify(expected);

console.log("Equal:", equalAtRuntime ? "✅" : "❌"); //=> Equal: ✅

从TSPlayground编译的JS:

"use strict";
function createNameMap(items) {
    const map = {};
    for (const item of items)
        map[item["name"]] = item;
    return map;
}
const actual = createNameMap([
    { name: "keyOne", extraField: 1 },
    { name: "keyTwo", extraField: 2 },
]);
//^^^^^^^^
// Use a const assertion to infer literal property values
actual.keyOne.name;
//^?
actual.keyOne.extraField;
//^?
actual.keyTwo.name;
//^?
actual.keyTwo.extraField;
//^?
const expected = {
    keyOne: { name: "keyOne", extraField: 1 },
    keyTwo: { name: "keyTwo", extraField: 2 },
};
const equalAtRuntime = JSON.stringify(actual) === JSON.stringify(expected);
console.log("Equal:", equalAtRuntime ? "✅" : "❌"); //=> Equal: ✅

相关问题