Typescript 创建类型脚本索引类型,其属性为“[Y的键]:Partial<;X>;`,当每个选项都与“默认值:部分”组合时<;X>;`属性,而不是部分属性

Typescript 创建类型脚本索引类型,其属性为“[Y的键]:Partial<;X>;`,当每个选项都与“默认值:部分”组合时<;X>;`属性,而不是部分属性,typescript,partial-classes,Typescript,Partial Classes,考虑TypeScript中的以下类型定义: enum Environment { Local = 'local', Prod = 'prod' } type EnvironmentConfig = { isCustomerFacing: boolean, serverUrl: string } type DefaultBaseConfig<T> = { default: T } type EnvironmentBaseConfig<T> = {

考虑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
  }
};

您想要的
BaseConfig
实际上更像是一种自引用,而不是TypeScript中的特定类型。也就是说,给定一个特定的候选类型
T
,您可以检查它是否可分配给
BaseConfigConstraint
规则,但您会发现很难/不可能在单个TypeScript对象类型中表达“遵守此规则的所有类型”

在这种情况下,我通常编写一个helper identity函数,它接受一个参数并返回它,但只接受类型为
T extends BaseConfigConstraint
的参数,以获得
BaseConfigConstraint
的合适定义。大概是这样的:

const asBaseConfig = <T extends
  { default: Partial<EnvironmentConfig> } &
  Record<Environment,
    Partial<EnvironmentConfig> &
    Omit<EnvironmentConfig, keyof T['default']>
  >
>(baseConfig: T) => baseConfig;
这正是您想要的错误。您可以通过将
serverUrl
添加到
prod
default
来修复错误。那很好

请注意,使用约束而不是类型意味着您将要提供参数或类型为
BaseConfig
的属性的任何函数或类型现在都需要是具有与此约束对应的类型参数的泛型函数或类型。这可能是您愿意在代码库中执行的操作,也可能不是


谢谢你的回答。这似乎是一个合理的解决方案,我可能会使用它。这几乎是我需要的工作。是否有一种方法允许
默认值
不必包含所有可能的属性,以便环境键控配置部分可以添加不在
默认值
中的新属性?假设
local
想要定义
clientId:string
,但是在
default
部分中没有默认值。这能适应吗?如果你回答了,请只添加代码,不要更改现有代码,因为这很有启发性。不过,我刚刚发现了一个问题,这使问题变得更加复杂。如果需要重写的属性存在于子属性的子属性中,则答案中的解决方案将失败,因为它只查看第一级属性。我想这是可能的,但很难做到正确。那太不幸了。我希望!!!我的错误是没有提供一个包含子属性的示例,这导致了一个更简单的假设,即所有属性都位于顶层。是的,我没有考虑子属性合并。这将变得更加复杂。也许有可能,但我现在不确定
const asBaseConfig = <T extends
  { default: Partial<EnvironmentConfig> } &
  Record<Environment,
    Partial<EnvironmentConfig> &
    Omit<EnvironmentConfig, keyof T['default']>
  >
>(baseConfig: T) => baseConfig;
const baseConfig = asBaseConfig({
  default: {
    isCustomerFacing: false
  },
  local: {
    serverUrl: 'https://local.example.com'
  },
  prod: { // error!
//~~~~ <-- Property 'serverUrl' is missing 
    isCustomerFacing: true,
  }
});