条件类型不能像 return 类型那样正常工作
Condtional type is not working properly as return type
我正在尝试为我的桌面应用程序制作一个设置系统。具有包含多个设置的配置,例如 add_while_paused
、min_chars
和 startup_settings
(这就是问题所在)
因此,我正在尝试使用 class 为 get_value
、update_value
、transform_value
等设置创建多个功能...
我要存档以下结果:
const a = new Setting("add_while_paused");
a.get_value(); // true
const b = new Setting("startup_settings"); // Error: Property 'child_name' is missing in type ...
b.get_value();
const c = new Setting("startup_settings", "delay")
c.get_value(); // 53
但是,get_value
抛出错误:
Type 'Config[BaseKey]' is not assignable to type 'HasChild<BaseKey, Config[BaseKey][ChildKey], Config[BaseKey]>'.
Type 'string | number | boolean | StartupSettings' is not assignable to type 'HasChild<BaseKey, Config[BaseKey][ChildKey], Config[BaseKey]>'.
Type 'string' is not assignable to type 'HasChild<BaseKey, Config[BaseKey][ChildKey], Config[BaseKey]>'.(2322)
这意味着方法中的 returned 值与 return 类型中的条件类型不匹配,但这是怎么回事?我很确定条件是正确的,我测试过没有问题。
HasChild<"min_chars", true, false> // false
HasChild<"add_while_paused", true, false> // false
HasChild<"startup_settings", true, false> // true
这是我的可重现示例:
declare const $symbol: unique symbol;
interface NestedSetting {
[$symbol]?: never;
}
interface StartupSettings extends NestedSetting {
enabled: boolean;
delay: number;
}
interface Config {
add_while_paused: boolean;
min_chars: number;
note: string;
startup_settings: StartupSettings;
}
type BaseKeys = keyof Config;
type HasChild<BK extends BaseKeys, True, False> = Config[BK] extends NestedSetting
? True
: False;
type ChildKeysOf<BK extends BaseKeys> = HasChild<BK, keyof Config[BK], never>;
const config: Config = {
add_while_paused: true,
min_chars: 53,
note: "Hello World!",
startup_settings: { enabled: true, delay: 56 }
}
class Setting<BaseKey extends keyof Config, ChildKey extends ChildKeysOf<BaseKey> = never> {
public base_name: BaseKey;
public child_name: ChildKey;
public constructor(base_name: BaseKey, child_name: ChildKey) {
this.base_name = base_name;
this.child_name = child_name;
}
public get_value(): HasChild<BaseKey, Config[BaseKey][ChildKey], Config[BaseKey]> {
if (this.child_name) return config[this.base_name][this.child_name];
return config[this.base_name]
}
}
Also, please don't mind the NestedSetting
type, I know I can just
check for object
instead but I did it like this because object
would match functions and arrays, not just "true objects"
好吧,这可能不是您需要的,但我找到了解决方案。
问题在于,虽然 HasChild
确定了 return 类型,但您最终也会得到类似的结果:
public get_value(): number {
if (this.child_name) {
return config[this.base_name][this.child_name]
}
return config[this.base_name]
}
Typescript 确定两个 return 语句具有不同的类型(例如,SystemSettings | number
),因此您会收到编译器错误。
我认为解决这个问题的唯一方法是创建两个实现,一个用于基本键,另一个用于子键,但它们共享一个公共接口。
并不是说这会增加一些冗长,但它似乎是类型安全的(假设您像我一样注释新 BaseSetting/ChildSetting 的类型。同样,您可以将其推断为类型,或使用类型守卫。实际上,我认为您在传递这些设置包装器时可能会遇到问题,因为这样做会丢失特定类型。您可能会成为 getting/setting 来自具有任意值的 UI 的值类型(如字符串),因此您需要清理值,这意味着在设置实现可以 get/set 值之前添加一些东西来转换值。这似乎使包装器的需要无效 class groks 是这样打字的,除非你可以依赖 JS 的强制。
备注:
- 我简化了
base_name
和 child_name
道具
- 我添加了
set_value
来充实用例
- 它可以编译,但我不确定它是否能解决您更广泛的问题;-)
declare const $symbol: unique symbol;
interface NestedSetting {
[$symbol]?: never;
}
interface StartupSettings extends NestedSetting {
enabled: boolean;
delay: number;
}
interface Config {
add_while_paused: boolean;
min_chars: number;
note: string;
startup_settings: StartupSettings;
}
type BaseKeys = keyof Config;
type HasChild<BK extends BaseKeys, True, False> = Config[BK] extends NestedSetting
? True
: False;
type ChildKeysOf<BK extends BaseKeys> = HasChild<BK, keyof Config[BK], never>;
const config: Config = {
add_while_paused: true,
min_chars: 53,
note: "Hello World!",
startup_settings: { enabled: true, delay: 56 }
}
interface Setting<T> {
get_value(): T
set_value(value: T): void
}
class BaseSetting<BaseKey extends keyof Config> implements Setting<Config[BaseKey]> {
public constructor(public readonly base_name: BaseKey) { }
public get_value(): Config[BaseKey] {
return config[this.base_name]
}
set_value(value: Config[BaseKey]): void {
config[this.base_name] = value
}
}
class ChildSetting<BaseKey extends keyof Config, ChildKey extends ChildKeysOf<BaseKey>> implements Setting<Config[BaseKey][ChildKey]> {
public constructor(
public readonly base_name: BaseKey,
public readonly child_name: ChildKey
) { }
public get_value(): Config[BaseKey][ChildKey] {
return config[this.base_name][this.child_name]
}
set_value(value: Config[BaseKey][ChildKey]): void {
config[this.base_name][this.child_name] = value
}
}
const delay: Setting<number> = new ChildSetting("startup_settings", "delay")
delay.get_value()
delay.set_value(200)
const enabled: Setting<boolean> = new ChildSetting("startup_settings", "enabled")
enabled.get_value()
enabled.set_value(true)
const min_chars: Setting<number> = new BaseSetting("min_chars")
min_chars.get_value()
min_chars.set_value(10)
我正在尝试为我的桌面应用程序制作一个设置系统。具有包含多个设置的配置,例如 add_while_paused
、min_chars
和 startup_settings
(这就是问题所在)
因此,我正在尝试使用 class 为 get_value
、update_value
、transform_value
等设置创建多个功能...
我要存档以下结果:
const a = new Setting("add_while_paused");
a.get_value(); // true
const b = new Setting("startup_settings"); // Error: Property 'child_name' is missing in type ...
b.get_value();
const c = new Setting("startup_settings", "delay")
c.get_value(); // 53
但是,get_value
抛出错误:
Type 'Config[BaseKey]' is not assignable to type 'HasChild<BaseKey, Config[BaseKey][ChildKey], Config[BaseKey]>'.
Type 'string | number | boolean | StartupSettings' is not assignable to type 'HasChild<BaseKey, Config[BaseKey][ChildKey], Config[BaseKey]>'.
Type 'string' is not assignable to type 'HasChild<BaseKey, Config[BaseKey][ChildKey], Config[BaseKey]>'.(2322)
这意味着方法中的 returned 值与 return 类型中的条件类型不匹配,但这是怎么回事?我很确定条件是正确的,我测试过没有问题。
HasChild<"min_chars", true, false> // false
HasChild<"add_while_paused", true, false> // false
HasChild<"startup_settings", true, false> // true
这是我的可重现示例:
declare const $symbol: unique symbol;
interface NestedSetting {
[$symbol]?: never;
}
interface StartupSettings extends NestedSetting {
enabled: boolean;
delay: number;
}
interface Config {
add_while_paused: boolean;
min_chars: number;
note: string;
startup_settings: StartupSettings;
}
type BaseKeys = keyof Config;
type HasChild<BK extends BaseKeys, True, False> = Config[BK] extends NestedSetting
? True
: False;
type ChildKeysOf<BK extends BaseKeys> = HasChild<BK, keyof Config[BK], never>;
const config: Config = {
add_while_paused: true,
min_chars: 53,
note: "Hello World!",
startup_settings: { enabled: true, delay: 56 }
}
class Setting<BaseKey extends keyof Config, ChildKey extends ChildKeysOf<BaseKey> = never> {
public base_name: BaseKey;
public child_name: ChildKey;
public constructor(base_name: BaseKey, child_name: ChildKey) {
this.base_name = base_name;
this.child_name = child_name;
}
public get_value(): HasChild<BaseKey, Config[BaseKey][ChildKey], Config[BaseKey]> {
if (this.child_name) return config[this.base_name][this.child_name];
return config[this.base_name]
}
}
Also, please don't mind the
NestedSetting
type, I know I can just check forobject
instead but I did it like this becauseobject
would match functions and arrays, not just "true objects"
好吧,这可能不是您需要的,但我找到了解决方案。
问题在于,虽然 HasChild
确定了 return 类型,但您最终也会得到类似的结果:
public get_value(): number {
if (this.child_name) {
return config[this.base_name][this.child_name]
}
return config[this.base_name]
}
Typescript 确定两个 return 语句具有不同的类型(例如,SystemSettings | number
),因此您会收到编译器错误。
我认为解决这个问题的唯一方法是创建两个实现,一个用于基本键,另一个用于子键,但它们共享一个公共接口。
并不是说这会增加一些冗长,但它似乎是类型安全的(假设您像我一样注释新 BaseSetting/ChildSetting 的类型。同样,您可以将其推断为类型,或使用类型守卫。实际上,我认为您在传递这些设置包装器时可能会遇到问题,因为这样做会丢失特定类型。您可能会成为 getting/setting 来自具有任意值的 UI 的值类型(如字符串),因此您需要清理值,这意味着在设置实现可以 get/set 值之前添加一些东西来转换值。这似乎使包装器的需要无效 class groks 是这样打字的,除非你可以依赖 JS 的强制。
备注:
- 我简化了
base_name
和child_name
道具 - 我添加了
set_value
来充实用例 - 它可以编译,但我不确定它是否能解决您更广泛的问题;-)
declare const $symbol: unique symbol;
interface NestedSetting {
[$symbol]?: never;
}
interface StartupSettings extends NestedSetting {
enabled: boolean;
delay: number;
}
interface Config {
add_while_paused: boolean;
min_chars: number;
note: string;
startup_settings: StartupSettings;
}
type BaseKeys = keyof Config;
type HasChild<BK extends BaseKeys, True, False> = Config[BK] extends NestedSetting
? True
: False;
type ChildKeysOf<BK extends BaseKeys> = HasChild<BK, keyof Config[BK], never>;
const config: Config = {
add_while_paused: true,
min_chars: 53,
note: "Hello World!",
startup_settings: { enabled: true, delay: 56 }
}
interface Setting<T> {
get_value(): T
set_value(value: T): void
}
class BaseSetting<BaseKey extends keyof Config> implements Setting<Config[BaseKey]> {
public constructor(public readonly base_name: BaseKey) { }
public get_value(): Config[BaseKey] {
return config[this.base_name]
}
set_value(value: Config[BaseKey]): void {
config[this.base_name] = value
}
}
class ChildSetting<BaseKey extends keyof Config, ChildKey extends ChildKeysOf<BaseKey>> implements Setting<Config[BaseKey][ChildKey]> {
public constructor(
public readonly base_name: BaseKey,
public readonly child_name: ChildKey
) { }
public get_value(): Config[BaseKey][ChildKey] {
return config[this.base_name][this.child_name]
}
set_value(value: Config[BaseKey][ChildKey]): void {
config[this.base_name][this.child_name] = value
}
}
const delay: Setting<number> = new ChildSetting("startup_settings", "delay")
delay.get_value()
delay.set_value(200)
const enabled: Setting<boolean> = new ChildSetting("startup_settings", "enabled")
enabled.get_value()
enabled.set_value(true)
const min_chars: Setting<number> = new BaseSetting("min_chars")
min_chars.get_value()
min_chars.set_value(10)