【问题标题】:Creating a discriminated union for a typescript function为打字稿函数创建可区分的联合
【发布时间】:2018-11-22 22:05:46
【问题描述】:

这是此处解决的问题的延续: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
});

将输入或返回值更改为 any 有效,但显然我想避免这种情况。我尝试创建一个 FooInterface 和 BarInterface 扩展的 AllFooBarInterfaces,如下所示:

// 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
};

【问题讨论】:

    标签: typescript


    【解决方案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&lt;FooBarTypes&gt; 将不起作用,因为返回的对象将被键入为{ type: FooBarTypes; data: FooInterface | BarInterface; },而FooBarAction&lt;FooBarTypes&gt; 被解析为{ type: "FOO"; data: FooInterface; } | { type: "BAR"; data: BarInterface; }

    在这种情况下,我们也可以使用开关来让编译器相信输出类型是正确的,但是由于开关的每个分支都将具有相同的代码,这似乎有点矫枉过正:

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

    【讨论】:

    • 哇。我试图做一些似乎不可能的事情。无论如何,非常感谢您的帮助。您似乎说FoobarAction&lt;FooBarTypes&gt; 解决了两个不同的问题。我猜后者应该是FoobarAction&lt;any&gt;。是否存在任何实际可能出错的情况?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-08-22
    • 2020-10-01
    • 2021-10-12
    • 2018-11-01
    • 2019-01-15
    • 2019-08-27
    • 2022-01-13
    相关资源
    最近更新 更多