我正在开发一个 TypeScript 函数,它根据输入参数返回不同的对象。返回类型是一个元组,其中前两个元素分别是数字和字符串,第三个元素是对象。返回对象的结构根据输入而变化。这是我的函数的简化版本:
function a(n: number) {
if (n == 2)
return [0, "", { some: "data" }];
if (n == 3)
return [0, "", { next: "step" }];
return [0, "", { other: "some" }];
}
function b() {
return [1, "some", {bebe: "baba"}]
}
我希望 TypeScript 根据
{ some: "data" }
的值推断元组中第三个元素的类型({ next: "step" }
、{ other: "some" }
或 n
),这样当我使用 a
的返回值时
,TypeScript 可以根据已知的返回类型为对象的字段提供自动补全功能。
例如:
const result = a(2);
console.log(result[2].some); // Should know that `some` is a valid field
const result1 = b()
console.log(result1[2].bebe)
但是,我正在努力定义函数的返回类型
a
,以允许 TypeScript 正确推断可能的对象结构,而无需事先显式定义所有可能的返回类型。
TypeScript 中有没有一种方法可以根据运行时条件动态推断元组返回类型中对象的结构?我正在寻找一种解决方案,理想情况下不需要我显式枚举所有可能的对象形状作为函数返回类型的一部分。
我尝试这样做,但收到错误“返回类型注释循环引用自身。”
function a(n: number): [string, Record<keyof (ReturnType<typeof a>[2]), any>] {
if (n == 2) return ["", { some: "data" }]
if (n == 3) return ["", { next: "step" }]
return ["", { other: "some" }]
}
我找到了适合我的任务的解决方案,但现在我有一个问题 - 我怎样才能做到这一点,以免在声明函数时使用新变量的创建。
function hasFailedResult<D, K extends keyof any>(func: (args: any) => [number, string, Partial<Record<K, D>>]) {
return func as (args: any) => { 0: number, 1: string, 2: Partial<Record<K, D>> };
}
const someFunc = hasFailedResult(function some(a: number) {
if (a == 1)
return [0, "", { next: "me" }]
return [0, "", { some: "data" }]
})
someFunc(1)[2].
// -----------^next?: string
// -----------^some?: string
我的主要任务是用函数修饰返回的数据类型,明确指定哪些字段肯定会在那里,但同时我不想丢失 TS 本身生成的类型。我找到的解决方案是创建一个装饰器函数,通过泛型转换函数的类型。
function hasFailedResult<D, K extends keyof any>(func: (args: any) => [number, string, Partial<Record<K, D>>]) {
return func as (args: any) => { 0: number, 1: string, 2: Partial<Record<K, D>> };
}
const someFunc = hasFailedResult(function some(a: number) {
if (a == 1)
return [0, "", { next: "me" }]
return [0, "", { some: "data" }]
})
someFunc(1)[2].
// -----------^next?: string
// -----------^some?: string
或
function hasFailedResult<
K extends keyof any,
T extends (args: any) => [number, string, D],
D extends Record<K, any>
>(func: T) {
return func as (args: any) => { 0: number, 1: string, 2: ReturnType<T>[2] };
}
const someFunc = hasFailedResult(function some(a: number) {
if (a == 1)
return [0, "", { next: "me" }]
return [0, "", { some: "data", data: "me" }]
})
const d = someFunc(1)[2];
/* it will be union type
const d: {
next: string;
} | {
some: string;
data: string;
}
*/