typescript 为记录的组合建立索引,其中一个记录的键是另一个记录的具有不同值的子集

n3h0vuf2  于 2023-04-07  发布在  TypeScript
关注(0)|答案(2)|浏览(102)

我有一个配置对象的键枚举,它呈现一个Material-UI Drawer Component,其中抽屉中的组件要么匹配一个单个组件,要么匹配每个子主题中的一组组件:

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 const TOPICS: Record<
  InsuranceTopicPrimaryKeys,
  TopicConfiguration | TopicConfigurationGrouped
> = {
  [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",
  },
};

配置(TopicConfigurationSecondaryTopicConfigurationTopicConfigurationGrouped)共享一个基本类型,它们扩展该基本类型:

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[];
}

然而,有时候我想查找InsuranceTopicPrimaryKeys.CLAIMS的配置,并且快捷方式必须向TS“证明”它的配置只会是TopicConfigurationGrouped类型。由于传统原因,我不能拆分配置,以及我想访问共享密钥的情况。
所以我希望我可以创建一个'keys'的子集,并拥有两个记录的并集(见下文),而不是一个值是类型并集的记录(Record<InsuranceTopicPrimaryKeys, TopicConfiguration | TopicConfigurationGrouped>)。

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

然后将TOPICS的类型设置为:

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

但是现在当我用这个helper函数来获取配置时:

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;

我得到这个错误:
元素隐式具有“any”类型,因为类型“InsuranceTopicPrimaryKeys”的表达式不能用于索引类型“Record〈InsuranceTopicPrimaryKeys,TopicConfiguration〉”|Record〈InsuranceTopicPrimaryKeys.CLAIMS,TopicConfigurationGrouped〉'。属性“[InsuranceTopicPrimaryKeys.POLICY_HOLDERS]”在类型“Record〈InsuranceTopicPrimaryKeys,TopicConfiguration〉”上不存在|记录〈InsuranceTopicPrimaryKeys.CLAIMS,TopicConfigurationGrouped〉’。(7053)
如何在配置Map对象(TOPICS和/或链接的playground TOPICS_TWO)中配置类型,以便此助手函数(和其他函数)可以推断InsuranceTopicPrimaryKeys类型可以索引不同记录的并集?
附加类型,使这一个工作的例子是在这个typescript操场

xe55xuns

xe55xuns1#

TOPICS_TWO的类型不应该是并集。由于InsuranceTopicPrimaryKeys包含TopicGroups,因此应首先排除TopicGroups。然后可以将该记录与新记录TopicGroups相交。

export const TOPICS_TWO: Record<
    Exclude<InsuranceTopicPrimaryKeys, TopicGroups>,
    TopicConfiguration
> &
    Record<TopicGroups, TopicConfigurationGrouped> = {

Playground

wydwbb8l

wydwbb8l2#

您收到的错误消息是因为TOPICS的类型与您定义为getConfig的返回类型的联合类型不兼容。类型TOPICS是具有属于InsuranceTopicPrimaryKeys枚举的键的记录,其值可以是TopicConfigurationTopicConfigurationGrouped。因此,当您尝试使用string类型的索引访问TOPICS的值时(由getConfig的configKey参数返回),TypeScript推断索引的类型为InsuranceTopicPrimaryKeys,这与string不兼容。这是因为TypeScript无法确定字符串是否实际上是枚举值之一。
修复此错误的一种方法是将getConfig的返回类型更改为仅TopicConfigurationTopicConfigurationGrouped的并集,因为SecondaryTopicConfiguration扩展了TopicConfiguration。当使用InsuranceTopicPrimaryKeys类型的索引访问时,TypeScript将能够推断TOPICS返回的值的正确类型。这是getConfig的更新版本:

const getConfig = (
  configKey: InsuranceTopicKeys
): TopicConfiguration | TopicConfigurationGrouped | undefined => {
  const topicConfig = TOPICS[configKey];
  if (topicConfig) {
    return topicConfig;
  }
  return Object.values(TOPICS).find(
    (topicConfig) => topicConfig?.secondaryKey === configKey
  )?.secondaryTopic;
};

在这个版本中,configKey的类型为InsuranceTopicKeys,它是InsuranceTopicPrimaryKeysInsuranceTopicSecondaryKeys的并集。这确保了用于访问TOPICS的索引是正确的类型。
请注意,您不需要定义单独的类型TopicGroups来表示属于TopicConfigurationGrouped的键。相反,您可以使用Record实用程序类型创建一个类型,将TOPICS的键Map到它们各自的值类型,然后使用条件类型过滤掉不属于TopicConfigurationGrouped的键。下面是一个示例:

type GroupedTopicKeys = {
  [K in keyof typeof TOPICS]: TOPICS[K] extends TopicConfigurationGrouped ? K : never;
}[keyof typeof TOPICS];

const groupedKeys: GroupedTopicKeys[] = ["CLAIMS"];

const groupedTopics: Record<GroupedTopicKeys, TopicConfigurationGrouped> = {
  [InsuranceTopicPrimaryKeys.CLAIMS]: {
    key: InsuranceTopicPrimaryKeys.CLAIMS,
    title: "Claims",
    component: TopicDrawerGroup,
    topicSelectorCreator: createMultiKeyedTopicSelector,
    sections: [
      SectionKeys.CLAIMANTS,
      SectionKeys.FULFILLED_CLAIMS,
      SectionKeys.UNFULFILLED_CLAIMS,
    ],
  },
};

在本例中,GroupedTopicKeys类型使用Map类型定义,该Map类型过滤掉TOPICS中不属于TopicConfigurationGrouped的键。结果类型是剩余键的并集。然后,groupedKeys变量被定义为属于TopicConfigurationGrouped的键的数组。最后,groupedTopics变量被定义为具有GroupedTopicKeys类型的键和TopicConfigurationGrouped类型的值的记录。

相关问题