带有类型的相关接口,但所需的属性不同

时间:2019-10-17 22:55:04

标签: typescript

这是我使用的API经常遇到的一种模式。我有一些对象,根据某种类型(在属性中描述),该对象将始终具有一些必需属性,某些属性始终是可选属性,而某些属性是某些特定类型时才需要(或完全存在)。我通常是这样解决问题的(也许用一些代码会更容易理解):

export type FoobarTypes = 'foo' | 'bar';

export interface FooBarBase {
  id:        string;
  type:      FoobarTypes;
  optional?: any;
}

export interface FooBarFoo extends FooBarBase {
  foo: any;
}

export interface FooBarBar extends FooBarBase {
  bar: any;
}


export type FooBar = FooBarFoo | FooBarBar;


// and to differentiate between the types:

export const isFooBarFoo  = (foobar: FooBar): foobar is FooBarFoo  =>
  (foobar as FooBarFoo).type === 'foo';

export const FooBarBar  = (foobar: FooBar): foobar is FooBarBar  =>
  (foobar as FooBarBar).type === 'bar';

它工作得很好,但是我觉得有点复杂,应该有一个更好,更合适的方法来实现它。还是要走的路?


编辑:这只是@Fyodor接受的答案的进一步简化。我只是将其放在此处,因此,如果有人有相同的问题,则比在注释中更容易提及。他的答案仍然是正确的,如果不是他的话,我也不会同意。

export type FoobarTypes = 'foo' | 'bar';

// all of the shared properties go here
export interface FooBarBase {
  id:        string;
  optional?: any;
}

// append the other, specific properties depending on type
export type FooBar<T extends FoobarTypes> =
  T extends 'foo' ? FooBarBase & {
    type: T;
    foo:  any;
  } : T extends 'bar' ? FooBarBase & {
    type: T;
    bar:  any;
  } : never;


// and the usage...
function FB(fb: FooBar<FoobarTypes>) {
  if (fb.type === 'foo') fb.foo = '1';
  if (fb.type === 'bar') fb.bar = '2';
}

1 个答案:

答案 0 :(得分:1)

您可以使用discriminated unions和泛型。这样可以根据type属性类型使用完全不同的类型。

export type FoobarTypes = 'foo' | 'bar';

type FooBarBase<T extends FoobarTypes> =
    T extends 'foo' ? 
    {
        id: string;
        type: T;   // T is 'foo'
        optional?: any;
        foo: any;
    } :
        T extends 'bar' ?
    {
        id: string;
        type: T;   // T is 'bar'
        optional?: any;
        bar: any;
    } : never;

type Foo = FooBarBase<'foo'>
type Bar = FooBarBase<'bar'>

function FB(fb: Foo | Bar) {
    if (fb.type === 'foo') {
        fb.foo = '1'   // TS knows, that fb has foo prop and doesn't have bar
    }
    else
    {
        fb.bar = '2'   // Here is opposite. fb only has 'bar'
    }
}