在 TypeScript 中扩展联合类型别名?
Extending Union Types Alias in TypeScript?
我试图限制某些字符串字段在编译时仅具有特定值。问题是这些值应该是可扩展的。
这是一个简化的例子:
type Foobar = 'FOO' | 'BAR';
interface SomeInterface<T extends Foobar> {
amember: T;
[key: string]: string; // this really has to stay
}
// let's test it
const yes = {
amember: 'FOO'
} as SomeInterface<'FOO'>; // compiles as expected
// const no = {
// amember: 'BAZ'
// } as SomeInterface<'BAZ'>; // Type '"BAZ"' does not satisfy the constraint 'Foobar' as expected
// so far so good
// Now the problem
abstract class SomeClass<T extends Foobar> {
private anotherMember: SomeInterface<T>;
}
type Foobarbaz = Foobar | 'BAZ';
class FinalClass extends SomeClass<Foobarbaz> { //no good anymore
}
错误是
Type 'Foobarbaz' does not satisfy the constraint 'Foobar'. Type
'"BAZ"' is not assignable to type 'Foobar'.
所以问题是:如何在 typescript 中将 'type' 限制为仅包含某些字符串,但可以将其扩展为其他字符串?
或者这是一个 XY 问题并且有明显更好的解决方案?
Typescript 2.3.4,但我想我可以升级到 2.4,如果那里有魔法的话。
how in typescript can I limit a 'type' to be of certain strings only, but have it extendable with other strings?
不知道你的 X 问题是什么,但你总是可以引入另一个泛型参数,并通过声明它扩展该参数来限制类型。 Typescript 2.3 supports default types for generic parameters,因此默认使用 Foobar
,您可以像以前一样将 SomeInterface
与一个参数一起使用,当您需要它来扩展其他内容时,您可以明确提供:
type Foobar = 'FOO' | 'BAR';
interface SomeInterface<T extends X, X extends string=Foobar> {
amember: T;
[key: string]: string; // this really has to stay
}
// let's test it
const yes = {
amember: 'FOO'
} as SomeInterface<'FOO'>; // compiles as expected
abstract class SomeClass<T extends X, X extends string=Foobar> {
private anotherMember: SomeInterface<T, X>;
}
type Foobarbaz = Foobar | 'BAZ';
class FinalClass extends SomeClass<Foobarbaz, Foobarbaz> {
}
更新
我想我现在明白问题所在了。一种解决方案是将像 Foobar 这样的联合类型编码为一些人工接口类型的 keyof
,仅用于表示键(不使用值类型,也无关紧要)。这样,通过扩展接口,您可以 "naturally" 扩展键集:
interface FoobarKeys { FOO: { }; BAR: { } };
type Foobar = keyof FoobarKeys;
interface SomeInterface<X extends FoobarKeys = FoobarKeys> {
amember: keyof X;
[key: string]: string; // this really has to stay
}
abstract class SomeClass<X extends FoobarKeys = FoobarKeys> {
protected anotherMember: SomeInterface<X> = {
amember: 'FOO'
};
protected amethod(): void {
this.bmethod(this.anotherMember); // no error
}
protected bmethod(aparam: SomeInterface<X>): void {
}
}
// let's extend it
interface FoobarbazKeys extends FoobarKeys { BAZ: {} };
type Foobarbaz = keyof FoobarbazKeys;
class FinalClass extends SomeClass<FoobarbazKeys> {
private f() {
this.bmethod({amember: 'BAZ'})
}
}
我认为您使用 "extendable" 一词的意义与关键字 extends
的意义不同。通过说类型是 "extendable" 你是说你希望能够 扩大 类型以接受 更多 值。但是当某些东西 extends
是一种类型时,这意味着您正在 缩小 类型以接受 更少的 值。
SomeInterface<T extends Foobar>
本质上只能是以下四种类型之一:
SomeInterface<'FOO'|'BAR'>
: amember
可以是 'FOO'
或 'BAR'
SomeInterface<'FOO'>
: amember
只能是'FOO'
SomeInterface<'BAR'>
: amember
只能是'BAR'
SomeInterface<never>
: amember
不能取任何值
我有点怀疑这是否真的是你想要的,但只有你自己清楚。
另一方面,如果您希望 SomeInterface<T>
被定义为 T
可以 始终 是 FOO
或 BAR
,但也可能是其他一些 string
值,您需要 TypeScript 不完全提供的东西,这将指定 T
的下限。像 SomeInterface<T
super
Foobar extends string>
这样的东西不是有效的 TypeScript。
但您可能只关心 amember
的类型,而不关心 T
。如果您希望 amember
是 FOO
或 BAR
,但也可能是其他一些 string
值,您可以这样指定它:
interface SomeInterface<T extends string = never> {
amember: Foobar | T;
[key: string]: string;
}
其中 T
只是您希望允许的额外文字的并集。如果您不想允许任何额外的内容,请使用 never
,或者省略类型参数(因为我已将 never
作为默认值)。
让我们看看实际效果:
const yes = {
amember: 'FOO'
} as SomeInterface; // good, 'FOO' is a Foobar
const no = {
amember: 'BAZ'
} as SomeInterface; // bad, 'BAZ' is not a Foobar
abstract class SomeClass<T extends string> {
private anotherMember: SomeInterface<T>;
}
class FinalClass extends SomeClass<'BAZ'> {
} // fine, we've added 'BAZ'
// let's make sure we did:
type JustChecking = FinalClass['anotherMember']['amember']
// JustChecking === 'BAZ' | 'FOO' | 'BAR'
我回答你的问题了吗?希望对您有所帮助。
要实现您的需要,您可以通过 &
符号使用 intersection。
type Foobar = 'FOO' | 'BAR';
type FoobarBaz = Foobar | & 'BAZ'; // or: 'BAZ' | & Foobar
我试图限制某些字符串字段在编译时仅具有特定值。问题是这些值应该是可扩展的。 这是一个简化的例子:
type Foobar = 'FOO' | 'BAR';
interface SomeInterface<T extends Foobar> {
amember: T;
[key: string]: string; // this really has to stay
}
// let's test it
const yes = {
amember: 'FOO'
} as SomeInterface<'FOO'>; // compiles as expected
// const no = {
// amember: 'BAZ'
// } as SomeInterface<'BAZ'>; // Type '"BAZ"' does not satisfy the constraint 'Foobar' as expected
// so far so good
// Now the problem
abstract class SomeClass<T extends Foobar> {
private anotherMember: SomeInterface<T>;
}
type Foobarbaz = Foobar | 'BAZ';
class FinalClass extends SomeClass<Foobarbaz> { //no good anymore
}
错误是
Type 'Foobarbaz' does not satisfy the constraint 'Foobar'. Type '"BAZ"' is not assignable to type 'Foobar'.
所以问题是:如何在 typescript 中将 'type' 限制为仅包含某些字符串,但可以将其扩展为其他字符串? 或者这是一个 XY 问题并且有明显更好的解决方案?
Typescript 2.3.4,但我想我可以升级到 2.4,如果那里有魔法的话。
how in typescript can I limit a 'type' to be of certain strings only, but have it extendable with other strings?
不知道你的 X 问题是什么,但你总是可以引入另一个泛型参数,并通过声明它扩展该参数来限制类型。 Typescript 2.3 supports default types for generic parameters,因此默认使用 Foobar
,您可以像以前一样将 SomeInterface
与一个参数一起使用,当您需要它来扩展其他内容时,您可以明确提供:
type Foobar = 'FOO' | 'BAR';
interface SomeInterface<T extends X, X extends string=Foobar> {
amember: T;
[key: string]: string; // this really has to stay
}
// let's test it
const yes = {
amember: 'FOO'
} as SomeInterface<'FOO'>; // compiles as expected
abstract class SomeClass<T extends X, X extends string=Foobar> {
private anotherMember: SomeInterface<T, X>;
}
type Foobarbaz = Foobar | 'BAZ';
class FinalClass extends SomeClass<Foobarbaz, Foobarbaz> {
}
更新
我想我现在明白问题所在了。一种解决方案是将像 Foobar 这样的联合类型编码为一些人工接口类型的 keyof
,仅用于表示键(不使用值类型,也无关紧要)。这样,通过扩展接口,您可以 "naturally" 扩展键集:
interface FoobarKeys { FOO: { }; BAR: { } };
type Foobar = keyof FoobarKeys;
interface SomeInterface<X extends FoobarKeys = FoobarKeys> {
amember: keyof X;
[key: string]: string; // this really has to stay
}
abstract class SomeClass<X extends FoobarKeys = FoobarKeys> {
protected anotherMember: SomeInterface<X> = {
amember: 'FOO'
};
protected amethod(): void {
this.bmethod(this.anotherMember); // no error
}
protected bmethod(aparam: SomeInterface<X>): void {
}
}
// let's extend it
interface FoobarbazKeys extends FoobarKeys { BAZ: {} };
type Foobarbaz = keyof FoobarbazKeys;
class FinalClass extends SomeClass<FoobarbazKeys> {
private f() {
this.bmethod({amember: 'BAZ'})
}
}
我认为您使用 "extendable" 一词的意义与关键字 extends
的意义不同。通过说类型是 "extendable" 你是说你希望能够 扩大 类型以接受 更多 值。但是当某些东西 extends
是一种类型时,这意味着您正在 缩小 类型以接受 更少的 值。
SomeInterface<T extends Foobar>
本质上只能是以下四种类型之一:
SomeInterface<'FOO'|'BAR'>
:amember
可以是'FOO'
或'BAR'
SomeInterface<'FOO'>
:amember
只能是'FOO'
SomeInterface<'BAR'>
:amember
只能是'BAR'
SomeInterface<never>
:amember
不能取任何值
我有点怀疑这是否真的是你想要的,但只有你自己清楚。
另一方面,如果您希望 SomeInterface<T>
被定义为 T
可以 始终 是 FOO
或 BAR
,但也可能是其他一些 string
值,您需要 TypeScript 不完全提供的东西,这将指定 T
的下限。像 SomeInterface<T
super
Foobar extends string>
这样的东西不是有效的 TypeScript。
但您可能只关心 amember
的类型,而不关心 T
。如果您希望 amember
是 FOO
或 BAR
,但也可能是其他一些 string
值,您可以这样指定它:
interface SomeInterface<T extends string = never> {
amember: Foobar | T;
[key: string]: string;
}
其中 T
只是您希望允许的额外文字的并集。如果您不想允许任何额外的内容,请使用 never
,或者省略类型参数(因为我已将 never
作为默认值)。
让我们看看实际效果:
const yes = {
amember: 'FOO'
} as SomeInterface; // good, 'FOO' is a Foobar
const no = {
amember: 'BAZ'
} as SomeInterface; // bad, 'BAZ' is not a Foobar
abstract class SomeClass<T extends string> {
private anotherMember: SomeInterface<T>;
}
class FinalClass extends SomeClass<'BAZ'> {
} // fine, we've added 'BAZ'
// let's make sure we did:
type JustChecking = FinalClass['anotherMember']['amember']
// JustChecking === 'BAZ' | 'FOO' | 'BAR'
我回答你的问题了吗?希望对您有所帮助。
要实现您的需要,您可以通过 &
符号使用 intersection。
type Foobar = 'FOO' | 'BAR';
type FoobarBaz = Foobar | & 'BAZ'; // or: 'BAZ' | & Foobar