避免将打字稿投射在开关内

时间:2018-11-20 11:56:49

标签: typescript casting typescript-typings typescript-generics

考虑以下代码:

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

type FooBarTypes = "FOO" | "BAR";

export interface FooBarAction<T extends FooBarTypes> {
  type: T;
  data: FooBarTypeMap[T];
}

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

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

现在,我想避免像在doSomthingBasedOnType中看到的那样进行强制转换(动作为FooBarAction <“ FOO”>),因为接口的明确定义使其成为此开关内部的唯一可能性。我可以在代码中进行一些更改以使其正常工作吗,或者这仅仅是TypeScript中的错误?

1 个答案:

答案 0 :(得分:2)

您需要将FooBarAction转换为有区别的联合。目前您的FooBarAction版本不是很严格,而type必须是"FOO" | "BAR"之一,而data必须是FooBarTypeMap[FooBarTypes] = FooInterface | BarInterface之一,没有关系两者之间。因此可以允许:

let o : FooBarAction2<FooBarTypes> = {
  type: "BAR",
  data: {} as FooInterface
}

有区别的联合版本如下:

export type FooBarAction = {
  type: "FOO";
  data: FooInterface;
} | {
  type: "BAR";
  data: BarInterface;
}

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

// We use extract to get a specific type from the union
const FooAction = (action: Extract<FooBarAction, { type: "FOO" }>): void => {
  //do something with action.data
};

您还可以使用条件类型的分布行为从类型的并集创建一个并集:

interface FooInterface { foo: number}
interface BarInterface { bar: number}
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;


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

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