在typescript中扩展联合类型别名?

时间:2017-08-17 21:49:10

标签: 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
}

错误是

  

键入&#39; Foobarbaz&#39;不满足约束&#39; Foobar&#39;。类型   &#39;&#34; BAZ&#34;&#39;不能分配给&#39; Foobar&#39;。

所以问题是:我可以在打字稿中限制&#39;类型&#39;仅限某些字符串,但是可以使用其他字符串扩展吗? 或者这是一个XY问题,并且有一个明显更好的解决方案吗?

Typescript 2.3.4但我认为如果那里有魔力,我可以升级到2.4。

3 个答案:

答案 0 :(得分:9)

我认为您正在使用&#34;可扩展的&#34;与关键字extends的含义不同。通过说类型是&#34;可扩展&#34;您已经说过,您希望加宽类型以接受更多值。但是当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 总是 FOOBAR,也可能是其他一些string值,你想要TypeScript没有提供的东西,这将指定T的下限。像SomeInterface<T super Foobar extends string>这样的东西,它不是有效的TypeScript。

但您可能只关心 amember 的类型,而不是T。如果您希望amember成为FOOBAR,还有其他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'

我是否回答了你的问题?希望有所帮助。

答案 1 :(得分:2)

  

如何在打字稿中将'type'限制为仅限某些字符串,但是可以将其扩展为其他字符串吗?

不知道你的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,用于仅表示键(值类型未使用且无关紧要)。这样,通过扩展界面,您可以“自然地”扩展键集:

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'})
    } 
}

答案 2 :(得分:0)

要实现所需的功能,可以通过&符号使用intersection

type Foobar = 'FOO' | 'BAR';
type FoobarBaz = Foobar | & 'BAZ'; // or: 'BAZ' | & Foobar

Example of intersected type