如何利用可区分联合来推断函数的 return 类型
How to leverage discriminated union to infer return type of a function
鉴于以下类型、接口和下面的 getData 函数,我试图找到一种方法来利用可区分的联合,以便 TS 编译器可以将 getData(source: DOSources)
的 return 类型缩小到关联 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;
}
}
一种选择是使用 函数重载 为不同的输入类型指定不同的潜在 return 值。 Here is a sandbox link 用于以下代码。
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
参数,您可以只显式传递类型以确定输出而不是传递变量。同样,此选项 a playground link。
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);
}
你确实可以让编译器根据 DOTypes
discriminated union 和 source
参数的类型计算所需的 return 类型的 getData()
.您可以使 getData()
成为 generic function,其类型参数 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 }>
}
要查找与 K
关联的 DOTypes
可区分联合的成员,我们可以使用 the Extract
utility type。 Extract<DOTypes, {source: K}>
从 DOTypes
中选择 source
属性 属于可分配给 K
.
的类型的所有联合成员
注意我们要assert函数return是一个值(一个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
鉴于以下类型、接口和下面的 getData 函数,我试图找到一种方法来利用可区分的联合,以便 TS 编译器可以将 getData(source: DOSources)
的 return 类型缩小到关联 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;
}
}
一种选择是使用 函数重载 为不同的输入类型指定不同的潜在 return 值。 Here is a sandbox link 用于以下代码。
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
参数,您可以只显式传递类型以确定输出而不是传递变量。同样,此选项 a playground link。
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);
}
你确实可以让编译器根据 DOTypes
discriminated union 和 source
参数的类型计算所需的 return 类型的 getData()
.您可以使 getData()
成为 generic function,其类型参数 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 }>
}
要查找与 K
关联的 DOTypes
可区分联合的成员,我们可以使用 the Extract
utility type。 Extract<DOTypes, {source: K}>
从 DOTypes
中选择 source
属性 属于可分配给 K
.
注意我们要assert函数return是一个值(一个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