问题描述
在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}}和每个环境default
和local
依次必须是完整的(不是Partial )prod
,从而满足某种类型的要求沿EnvironmentConfig
的行。
在这里的示例情况下,BaseConfig
是有效的,因为与local
结合使用时,它具有两个属性。但是,default
无效,因为在prod
和serverUrl
的交集之间没有声明default
。
很显然,稍后,此配置对象将有条件地合并到某些代码中,这些代码采用prod
和BaseConfig
并返回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}}属性,并且
两种子类型的 - 属性:
-
Environment
和 - 具有
Partial<EnvironmentConfig>
的{{1}}属性的任何属性EnvironmentConfig
的对象。
-
Partial<EnvironmentConfig>
键上的换句话说,default
必须具有T
和T
中的键,所有键都必须具有Environment
属性,但是{{ 1}}属性必须存在于"default"
中。
让我们看看它在您的示例中如何工作:
Partial<EnvironmentConfig>
这正是您想要的错误。您可以通过将default
添加到Environment
或const baseConfig = asBaseConfig({
default: {
isCustomerFacing: false
},local: {
serverUrl: 'https://local.example.com'
},prod: { // error!
//~~~~ <-- Property 'serverUrl' is missing
isCustomerFacing: true,}
});
来纠正错误。这样很好。
请注意,拥有约束而不是类型意味着要给参数或类型为serverUrl
的属性的任何函数或类型现在都必须是泛型具有与此约束对应的类型参数的函数或类型。您可能不愿意在代码库中这样做。