创建TypeScript索引类型,当其每个属性与[default:Partial <X>`属性结合使用时,它们的属性[[Y的键]:Partial <X>`不属于局部

问题描述

在TypeScript中考虑以下类型定义:

enum Environment {
  Local = 'local',Prod = 'prod'
}

type EnvironmentConfig = {
  isCustomerFacing: boolean,serverUrl: string
}

type DefaultBaseConfig<T> = {
  default: T
}

type EnvironmentBaseConfig<T> = {
  [key in Environment]: T
}

type BaseConfig<T> = DefaultBaseConfig<T> | EnvironmentBaseConfig<T>;

// const baseConfig: ??? = {
const baseConfig: BaseConfig<Partial<EnvironmentConfig>> = {
  default: {
    isCustomerFacing: false
  },local: {
    serverUrl: 'https://local.example.com'
  },prod: {
    isCustomerFacing: true
  }
};

const baseConfig: ???和下一行的Partial<>结束时通知对象。我真正想要的是让baseConfig允许每个环境键属性Partial<EnvironmentConfig>,并允许default属性相同,而且还要求{ {1}}和每个环境defaultlocal依次必须是完整的(不是Partial )prod,从而满足某种类型的要求沿EnvironmentConfig的行。

在这里的示例情况下,BaseConfig是有效的,因为与local结合使用时,它具有两个属性。但是,default无效,因为在prodserverUrl的交集之间没有声明default

很显然,稍后,此配置对象将有条件地合并到某些代码中,这些代码采用prodBaseConfig并返回environmentName,并且可以保证如果为它提供了一个静态配置对象,该对象已由Typescript检查过,以符合所需的约束条件,则可以在运行时工作。

我一直对此感到困惑,被困住了。我知道如何进行条件类型和类型约束,例如EnvironmentConfig,但似乎无法弄清楚如何以及在何处将其应用于这种情况。

我如何实现这个目标?

到目前为止,这是我最好的尝试,但是,这当然不起作用:

T extends U ? T : never

解决方法

您想要的BaseConfig实际上更像是自引用generic constraint,而不是TypeScript中的特定类型。也就是说,给定特定的候选人类型T,您可以检查它是否可分配给BaseConfigConstraint<T>规则,但是很难/不可能表达“全部”符合此规则的类型”在单个TypeScript对象类型中。

在这种情况下,我通常编写一个辅助身份函数,该函数接受单个参数并返回它,但仅接受类型为T extends BaseConfigConstraint<T>的参数,以用于BaseConfigConstraint<T>的适当定义。像这样:

const asBaseConfig = <T extends
  { default: Partial<EnvironmentConfig> } &
  Record<Environment,Partial<EnvironmentConfig> &
    Omit<EnvironmentConfig,keyof T['default']>
  >
>(baseConfig: T) => baseConfig;

在这里,我们说baseConfig的类型必须为T,且具有:

  • 子类型为default的{​​{1}}属性,并且
  • 两种子类型的Partial<EnvironmentConfig>键上的
  • 属性:
    • Environment
    • 具有Partial<EnvironmentConfig>的{​​{1}}属性的任何属性EnvironmentConfig的对象。

换句话说,default必须具有TT中的键,所有键都必须具有Environment属性,但是{{ 1}}属性必须存在于"default"中。

让我们看看它在您的示例中如何工作:

Partial<EnvironmentConfig>

这正是您想要的错误。您可以通过将default添加到Environmentconst baseConfig = asBaseConfig({ default: { isCustomerFacing: false },local: { serverUrl: 'https://local.example.com' },prod: { // error! //~~~~ <-- Property 'serverUrl' is missing isCustomerFacing: true,} }); 来纠正错误。这样很好。

请注意,拥有约束而不是类型意味着要给参数或类型为serverUrl的属性的任何函数或类型现在都必须是泛型具有与此约束对应的类型参数的函数或类型。您可能不愿意在代码库中这样做。

Playground link to code