如何使Typescript推断对象的键,但定义其值的类型?

mrwjdhj3  于 2023-10-22  发布在  TypeScript
关注(0)|答案(3)|浏览(126)

我想定义一个对象的类型,但让typescript推断键,并且没有那么多的开销来创建和维护所有键的UnionType。
键入一个对象将允许所有字符串作为键:

const elementsTyped: { 
    [key: string]: { nodes: number, symmetric?: boolean }
} = {
    square: { nodes: 4, symmetric: true },
    triangle: { nodes: 3 }
}

function isSymmetric(elementType: keyof typeof elementsTyped): boolean {
    return elementsTyped[elementType].symmetric;
}
isSymmetric('asdf'); // works but shouldn't

推断整个对象将显示错误,并允许所有类型的值:

const elementsInferred = {
    square: { nodes: 4, symmetric: true },
    triangle: { nodes: 3 },
    line: { nodes: 2, notSymmetric: false /* don't want that to be possible */ }
}

function isSymmetric(elementType: keyof typeof elementsInferred): boolean {
    return elementsInferred[elementType].symmetric; 
    // Property 'symmetric' does not exist on type '{ nodes: number; }'.
}

我得到的最接近的结果是这样的,但它不想像那样维护密钥集:

type ElementTypes = 'square' | 'triangle'; // don't want to maintain that :(
const elementsTyped: { 
    [key in ElementTypes]: { nodes: number, symmetric?: boolean }
} = {
    square: { nodes: 4, symmetric: true },
    triangle: { nodes: 3 },
    lines: { nodes: 2, notSymmetric: false } // 'lines' does not exist in type ...
    // if I add lines to the ElementTypes as expected => 'notSymmetric' does not exist in type { nodes: number, symmetric?: boolean }
}

function isSymmetric(elementType: keyof typeof elementsTyped): boolean {
    return elementsTyped[elementType].symmetric;
}
isSymmetric('asdf'); // Error: Argument of type '"asdf"' is not assignable to parameter of type '"square" | "triangle"'.

有没有更好的方法来定义对象而不维护键集?

h79rfbju

h79rfbju1#

因此,您需要推断键但限制值类型的东西,并使用多余的属性检查来禁止额外的属性。我认为获得这种行为的最简单方法是引入一个helper函数:

// Let's give a name to this type
interface ElementType {
  nodes: number,
  symmetric?: boolean
}

// helper function which infers keys and restricts values to ElementType
const asElementTypes = <T>(et: { [K in keyof T]: ElementType }) => et;

这个辅助函数从et的Map类型推断T的类型。现在你可以这样使用它:

const elementsTyped = asElementTypes({
  square: { nodes: 4, symmetric: true },
  triangle: { nodes: 3 },
  line: { nodes: 2, notSymmetric: false /* error where you want it */} 
});

结果elementsTyped的类型(一旦修复错误)将具有推断的键squaretriangleline,值为ElementType
希望对你有用。祝你好运!

oknrviil

oknrviil2#

TypeScript >=4.9.0

TypeScript 4.9.0添加了satisfies关键字,可用于在推断键时约束对象的值。

type ElementValue = {
  nodes: number;
  symmetric?: boolean;
};

const elements = {
  square: { nodes: 4, symmetric: true },
  triangle: { nodes: 3 },
} satisfies Record<string, ElementValue>;

type Elements = typeof elements;
type ElementType = keyof Elements;

function isSymmetric(elementType: ElementType): boolean {
  const element = elements[elementType];
  return 'symmetric' in element && element.symmetric;
}

isSymmetric('asdf'); // doesn't work

TypeScript <4.9.0

中间函数可用于在推断键时约束对象的值。

type ElementValue = {
  nodes: number;
  symmetric?: boolean;
};

function typedElements<T extends Record<string, ElementValue>>(o: T) {
  return o;
}

const elements = typedElements({
  square: { nodes: 4, symmetric: true },
  triangle: { nodes: 3 },
});

type Elements = typeof elements;
type ElementType = keyof Elements;

function isSymmetric(elementType: ElementType): boolean {
  const element = elements[elementType];
  return 'symmetric' in element && element.symmetric;
}

isSymmetric('asdf'); // doesn't work
klsxnrf1

klsxnrf13#

另一个更简单的解决方案涉及使用enum而不是复杂的TS类型继承:

type SiteRoute = {
  title: string
  path: string
  isAdmin?: boolean
  disabled?: boolean
}

enum Routes {
    home = "home",
    events = "events",
    contacts = "contacts",
}

const ROUTES_DETAILS: Record<Routes, SiteRoute> = {
    [Routes.home]: {
        title: "Home",
        path: "/",
    },
    [Routes.events]: {
        title: "Events",
        path: "/events",
        disabled: true,

    },
    [Routes.contacts]: {
        title: "Contacts",
        path: "/contacts",
        isAdmin: true,
        disabled: true,
    },
}

ROUTES_DETAILS.home  // ok with autocomplete
ROUTES_DETAILS.asa   // Error: Property 'asa' does not exist on type

这个解决方案可读性更强,使用基本类型和enum。唯一的缺点是enum应该与一个字符串值配对,以便在自动完成中可读。

相关问题