繁体   English   中英

如何利用可区分联合来推断函数的返回类型

[英]How to leverage discriminated union to infer return type of a function

鉴于以下类型、接口和 getData 函数,我试图找到一种方法来利用可区分联合,以便 TS 编译器可以将getData(source: DOSources)的返回类型缩小到关联的DOTypes

// Expected behavior
const result = getData("dataObjectA");

// result.data should be a string but in this case the TS compiler will complain 
// that data does not have the toLowerCase() function
result.data.toLowerCase();

示例代码

interface DataObjectA {
  source: "dataObjectA";
  data: string;
}

interface DataObjectB {
  source: "dataObjectB";
  data: number;
}


type DOTypes = DataObjectA | DataObjectB
type DOSources = DOTypes["source"];

async function getData(source: DOSources) {
  const response = await fetch(`https://some-random-endpoint/`, {
    method: "GET",
    headers: {
      "Content-Type": "application/json",
    },
  });

  switch (source) {
    case "dataObjectA":
      return await response.json() as DataObjectA;
    case "dataObjectB":
      return await response.json() as DataObjectB;
  }
}

您确实可以让编译器计算所需的getData()返回类型,作为DOTypes区分联合和source参数类型的函数。 您可以使getData()成为一个泛型函数,其类型参数K extends DOSourcessource参数的类型。 例如:

async function getData<K extends DOSources>(source: K) {
  const response = await fetch(`https://some-random-endpoint/`, {
    method: "GET",
    headers: {
      "Content-Type": "application/json",
    },
  });

  return await response.json() as Extract<DOTypes, { source: K }>
}

要查找与K关联的DOTypes区分联合的成员,我们可以使用Extract实用程序类型 Extract<DOTypes, {source: K}>DOTypes选择source属性属于可分配给K的类型的所有联合成员。

请注意,我们必须断言该函数返回的值(对应于该类型的Promise ); 编译器无法验证。


让我们测试一下:

const resultA = await getData("dataObjectA"); // const result: DataObjectA
resultA.data.toLowerCase();

const resultB = await getData("dataObjectB"); // const result: DataObjectB
resultB.data.toFixed();

看起来挺好的。 每个结果都缩小到预期的类型。 如果您将联合放入,您只会从getData()获得联合:

const resultAOrB = await getData(Math.random() < 0.5 ? "dataObjectA" : "dataObjectB");
// const resultAOrB: DataObjectA | DataObjectB

Playground 链接到代码

一种选择是使用函数重载来为不同的输入类型指定不同的潜在返回值。 这是以下代码的沙箱链接

interface DataObjectA {
  source: "dataObjectA";
  data: string;
}

interface DataObjectB {
  source: "dataObjectB";
  data: number;
}

type DOTypes = DataObjectA | DataObjectB
type DOSources = DOTypes["source"];

async function getData(source: DataObjectA["source"]): Promise<DataObjectA>;
async function getData(source: DataObjectB["source"]): Promise<DataObjectB>;
async function getData(source: DOSources) {
  const response = await fetch(`https://some-random-endpoint/`, {
    method: "GET",
    headers: {
      "Content-Type": "application/json",
    },
  });

  return await response.json();
}

async function test() {
    // string
    const result = await getData("dataObjectA");
    result.data.toLowerCase();
    // number
    const result2 = await getData("dataObjectB");
    result2.data.toFixed(3);
}

话虽如此,如果您实际上没有使用source参数,您可以只显式传递类型来确定输出,而不是传递变量。 同样,此选项的操场链接

interface DataObjectA {
  source: "dataObjectA";
  data: string;
}

interface DataObjectB {
  source: "dataObjectB";
  data: number;
}

type DOTypes = DataObjectA | DataObjectB;
type DOSources = DOTypes["source"];

async function getData<T extends DOSources>(): Promise<
  T extends DataObjectA["source"] ? DataObjectA : DataObjectB
> {
  const response = await fetch(`https://some-random-endpoint/`, {
    method: "GET",
    headers: {
      "Content-Type": "application/json",
    },
  });

  return await response.json();
}

async function test() {
  // string
  const result = await getData<"dataObjectA">();
  result.data.toLowerCase();
  // number
  const result2 = await getData<"dataObjectB">();
  result2.data.toFixed(3);
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM