为打字稿功能创建区分联合

时间:2018-11-22 10:27:13

标签: typescript

这是此处解决的问题的延续:Avoid typescript casting inside a switch 使用这种方法,我可以设置如下类型:

interface FooInterface {
  foo: number,
  type: "FOO"
}

interface BarInterface {
  bar: number,
  type: "BAR"
}

interface FooBarTypeMap {
  FOO: FooInterface;
  BAR: BarInterface;
}

type FooBarTypes = "FOO" | "BAR";

export type FooBarAction<T extends FooBarTypes> = T extends any ? {
  type: T;
  data: FooBarTypeMap[T];
} : never;


//I want to use this to create a function which returns a FooBarAction, of either type. But the following code fails on typings:   

const createFooBarAction = <T extends FooBarTypes>(fooBarData: FooBarTypeMap[T]): FooBarAction<T> => ({
  type: fooBarData.type,
  data: fooBarData
});

将输入值或返回值更改为任何都可以,但是显然我想避免这种情况。我尝试创建一个AllFooBarInterfaces,其中FooInterface和BarInterface扩展如下:

// Seems to not have any effect, but it might be a good practice anyway.
interface AllFooBarInterfaces<T extends FooBarTypes> {
  type: T
}

interface FooInterface extends AllFooBarInterfaces<"FOO">{
  foo: number,
}

interface BarInterface extends AllFooBarInterfaces<"BAR">{
  bar: number,
}

虽然我可以对上面的接口和类型的定义进行更改,但我仍然需要支持原始问题中提出的案例,为了方便访问,该问题已包含在下面。

const doSomthingBasedOnType = (action: FooBarAction<FooBarTypes>): void => {
  switch (action.type) {
    case "FOO":
      FooAction(action);
  }
};

const FooAction = (action: FooBarAction<"FOO">): void => {
  //do something with action.data
};

1 个答案:

答案 0 :(得分:1)

您的实现将无法正常工作,因为Typescript不允许将值分配给期望具有无法解析的通用参数的类型的位置。保留呼叫站点行为的最佳解决方案是添加具有泛型类型参数和正确实现签名的重载,该重载类型安全性稍差一些,但允许我们实际实现该功能:

function createFooBarAction <T extends FooBarTypes>(fooBarData: FooBarTypeMap[T]): FooBarAction<T>
function createFooBarAction (fooBarData: FooBarTypeMap[FooBarTypes]): FooBarAction<any> {
  return {
    type: fooBarData.type,
    data: fooBarData
  };
}

请注意在返回类型中使用any。不幸的是,这是必需的。归来 FooBarAction<FooBarTypes>将不起作用,因为在{ type: FooBarTypes; data: FooInterface | BarInterface; }解析为FooBarAction<FooBarTypes>时,返回的对象将被键入为{ type: "FOO"; data: FooInterface; } | { type: "BAR"; data: BarInterface; }

在这种情况下,我们也可以使用一个开关,以使编译器确信out类型是正确的,但是由于开关的每个分支都将具有相同的代码,因此似乎过分了:

function createFooBarAction <T extends FooBarTypes>(fooBarData: FooBarTypeMap[T]): FooBarAction<T>
function createFooBarAction (fooBarData: FooBarTypeMap[FooBarTypes]): FooBarAction<FooBarTypes> {
  switch (fooBarData.type) {
    case "FOO": return { type:fooBarData.type, data: fooBarData }
    case "BAR": return { type:fooBarData.type, data: fooBarData }
  }
}