You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

TypeScript中如何为混合类型配置对象设置正确索引类型

解决TypeScript配置对象的精确类型推断与索引问题

问题场景

我有一个用于配置Material-UI Drawer组件的对象,抽屉组件分为单个组件和分组组件两类。相关枚举、类型定义及配置对象如下:

枚举定义

export enum InsuranceTopicPrimaryKeys {
  POLICY_HOLDERS = "policyHolders",
  INSURANCE_PROVIDERS = "providers",
  INSURANCE_TYPE = "type",
  CLAIMS = "claims",
}

export enum InsuranceTopicSecondaryKeys {
  POLICY_MEMBERS = "policyMembers",
}

export type InsuranceTopicKeys =
  | InsuranceTopicPrimaryKeys
  | InsuranceTopicSecondaryKeys;

export enum SectionKeys {
  CLAIMANTS = "claimants",
  FULFILLED_CLAIMS = "fullfilledClaims",
  UNFULFILLED_CLAIMS = "unfullfilledClaims",
}

配置对象及类型

export interface BaseTopicConfig {
  key: InsuranceTopicKeys;
  title: string;
  secondaryKey?: InsuranceTopicSecondaryKeys;
  secondaryTopic?: SecondaryTopicConfiguration;
}

export interface TopicConfiguration extends BaseTopicConfig {
  topicSelector: TopicSelector;
  component: TopicComponent;
}
export interface SecondaryTopicConfiguration extends TopicConfiguration {
  primaryKey: InsuranceTopicPrimaryKeys;
}
export interface TopicConfigurationGrouped extends BaseTopicConfig {
  topicSelectorCreator: TopicSelectorCreator;
  component: TopicComponentGroup;
  sections: SectionKeys[];
}

export const TOPICS = {
  [InsuranceTopicPrimaryKeys.POLICY_HOLDERS]: {
    key: InsuranceTopicPrimaryKeys.POLICY_HOLDERS,
    secondaryKey: InsuranceTopicSecondaryKeys.POLICY_MEMBERS,
    title: "Policy Holders",
    component: (props: TopicDrawerProps) => (
      <TopicDrawer hasSecondary={true} {...props} />
    ),
    topicSelector: keyedTopicSelector.showAll,
    secondaryTopic: {
      key: InsuranceTopicSecondaryKeys.POLICY_MEMBERS,
      primaryKey: InsuranceTopicPrimaryKeys.POLICY_HOLDERS,
      title: "Policy Members",
      component: (props: TopicDrawerProps) => (
        <TopicDrawer isSecondary={true} {...props} />
      ),
      topicSelector: keyedTopicSelector.showRelated,
    },
  },
  [InsuranceTopicPrimaryKeys.INSURANCE_PROVIDERS]: {
    key: InsuranceTopicPrimaryKeys.INSURANCE_PROVIDERS,
    title: "Policy Holders",
    component: TopicDrawerSimple,
    topicSelector: keyedTopicSelector.showAll,
  },
  [InsuranceTopicPrimaryKeys.INSURANCE_TYPE]: {
    key: InsuranceTopicPrimaryKeys.INSURANCE_PROVIDERS,
    title: "Policy Holders",
    component: TopicDrawerSimple,
    topicSelector: keyedTopicSelector.showAll,
  },
  [InsuranceTopicPrimaryKeys.CLAIMS]: {
    key: InsuranceTopicPrimaryKeys.CLAIMS,
    title: "Claims",
    component: TopicDrawerGroup,
    topicSelectorCreator: createMultiKeyedTopicSelector,
    sections: [
      SectionKeys.CLAIMANTS,
      SectionKeys.FULFILLED_CLAIMS,
      SectionKeys.UNFULFILLED_CLAIMS,
    ],
  },
};

export type SectionConfig = {
  key: SectionKeys;
  title: string;
};

export const SECTIONS: Record<SectionKeys, SectionConfig> = {
  [SectionKeys.CLAIMANTS]: {
    key: SectionKeys.CLAIMANTS,
    title: "Claimants",
  },
  [SectionKeys.FULFILLED_CLAIMS]: {
    key: SectionKeys.FULFILLED_CLAIMS,
    title: "Fulfilled Claims",
  },
  [SectionKeys.UNFULFILLED_CLAIMS]: {
    key: SectionKeys.UNFULFILLED_CLAIMS,
    title: "Unfulfilled Claims",
  },
};

尝试的方案及错误

为了避免每次查询InsuranceTopicPrimaryKeys.CLAIMS时手动断言类型,我尝试定义键子集并设置TOPICS的类型为联合Record:

// subset that match config of type TopicConfigurationGrouped 
export type TopicGroups = InsuranceTopicPrimaryKeys.CLAIMS

const TOPICS: Record<InsuranceTopicPrimaryKeys, TopicConfiguration> | Record<TopicGroups, TopicConfigurationGrouped>

但在使用辅助函数时出现错误:

const isPrimaryTopic = (
  topicType: string
): topicType is InsuranceTopicPrimaryKeys =>
  Object.values(InsuranceTopicPrimaryKeys).includes(
    topicType as InsuranceTopicPrimaryKeys
  );

const getConfig = (
  configKey: string
):
  | SecondaryTopicConfiguration
  | TopicConfiguration
  | TopicConfigurationGrouped
  | undefined =>
  isPrimaryTopic(configKey)
    ? TOPICS[configKey as InsuranceTopicPrimaryKeys]
    : Object.values(TOPICS).find(
        (topicConfig) => topicConfig?.secondaryKey === configKey
      )?.secondaryTopic;

错误信息:

Element implicitly has an 'any' type because expression of type 'InsuranceTopicPrimaryKeys' can't be used to index type 'Record<InsuranceTopicPrimaryKeys, TopicConfiguration> | Record<InsuranceTopicPrimaryKeys.CLAIMS, TopicConfigurationGrouped>'.
Property '[InsuranceTopicPrimaryKeys.POLICY_HOLDERS]' does not exist on type 'Record<InsuranceTopicPrimaryKeys, TopicConfiguration> | Record<InsuranceTopicPrimaryKeys.CLAIMS, TopicConfigurationGrouped>'.(7053)


解决方案

问题出在联合Record类型无法让TypeScript确定所有主键都能安全索引,因为第二个Record只包含CLAIMS键,其他主键在该类型中不存在。我们需要为TOPICS定义一个精确的映射类型,让每个主键对应其具体的配置类型。

步骤1:定义精确的映射类型

创建一个类型,为每个InsuranceTopicPrimaryKeys成员指定对应的配置类型:

// 可选:先定义分组键的集合,便于后续维护
type GroupedTopicKeys = InsuranceTopicPrimaryKeys.CLAIMS;

// 自动生成映射类型:分组键对应TopicConfigurationGrouped,其余对应TopicConfiguration
type TopicConfigMap = {
  [K in InsuranceTopicPrimaryKeys]: K extends GroupedTopicKeys
    ? TopicConfigurationGrouped
    : TopicConfiguration;
};

步骤2:为TOPICS指定映射类型

将TOPICS的类型设置为TopicConfigMap,TypeScript会自动验证配置是否符合类型要求,同时精确推断每个键的返回类型:

export const TOPICS: TopicConfigMap = {
  // 原配置内容保持不变
};

现在访问TOPICS[InsuranceTopicPrimaryKeys.CLAIMS]时,TypeScript会自动推断它是TopicConfigurationGrouped类型,无需手动断言;访问其他主键则自动推断为TopicConfiguration类型。

步骤3:修正辅助函数

调整getConfig函数的类型,让它适配新的映射类型:

const getConfig = (
  configKey: string
): SecondaryTopicConfiguration | TopicConfigMap[InsuranceTopicPrimaryKeys] | undefined => {
  if (isPrimaryTopic(configKey)) {
    // 用keyof TopicConfigMap确保索引安全
    return TOPICS[configKey as keyof TopicConfigMap];
  }
  return Object.values(TOPICS).find(
    (topicConfig) => topicConfig?.secondaryKey === configKey
  )?.secondaryTopic;
};

内容的提问来源于stack exchange,提问作者AncientSwordRage

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.26 04:12:05