首页 > 解决方案 > 如何利用有区别的联合来推断函数的返回类型

问题描述

鉴于下面的类型、接口和 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;
  }
}

标签: javascripttypescriptclassgenericsunion

解决方案


您确实可以让编译器根据可区分联合和参数类型计算所需的getData()返回DOTypes类型source。您可以创建getData()一个泛型函数,其类型参数K extends DOSources是参数的类型source。例如:

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

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

请注意,我们必须断言该函数返回(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 代码链接


推荐阅读