网站搜索

Typescript:你能根据参数对象中单个键的值获取函数的返回类型吗?


我有一个 hook,它会进行 API 调用并缓存整个应用中使用的对象。

与其需要有一堆辅助函数来获取这个对象的特定值,不如传入对象的键并返回该键对应的值,该钩子还可以访问这些对象的数组并选择一个特定的对象,或者返回应用的默认值。

钩子函数,我在编写符合 TypeScript 输入和输出的类型定义时没有遇到困难。

这是我目前所做的:

type OddParams = {
    thing: number;
    thing2: number;
    key?: PotatoKey;
}

type Banana = {
    id: string;
    name: string;
}

type Potato = {
    id: string;
    name: string;
    phone: number;
    monkey: Banana;
}

type PotatoKey = keyof Potato;
type Params = OddParams | PotatoKey;

type ObjectType<T> = 
    T extends unknown ? Potato :
    T extends 'id' | 'name' ? string :
    T extends 'phone' ? number :
    T extends 'monkey' ? Banana :
    never;

function getPotato<T extends Params>(desc =  (undefined as unknown) as T): ObjectType<T>{
    console.log(desc);
    return '2' as any
};

getPotato() //returns type Potato (returns the default potato in implemntation )
getPotato('id') //returns type string (would return id of the default potato in implemntation )
getPotato({thing: 2, thing2: 1, key: 'id'}) //returns type string (would return id of potato[2][1] in implemntation )

仍然需要将对象部分添加到 ObjectType 变量中。即使只是字符串参数,也在寻找基于键声明的更好方法。上面我对每个键都使用了 T extends 'id' 等写法,但在实际生产问题中有 20 多个键,因此为每个返回类型使用大量三元运算符最终非常难以阅读。

有什么方法可以实现这一点吗?

答案#1

你的getPotato函数基本上需要三种不同的签名,以及三种对应确定匹配返回类型的方法。这可以通过函数重载来实现。

第一和第二个(分别是无参数和字符串参数)相当容易做到:

function getPotato(): Potato;
function getPotato<K extends keyof Potato>(key: K): Potato[K];

对于第三个重载,你需要从你提供的 params 对象中“取出”key 属性的值。在此之前,key 属性必须是 keyof Potato 类型:

type Params = {
    thing: number;
    thing2: number;
    key: keyof Potato;
}

function getPotato<T extends Params>(params: T): Potato[T['key']]

这句话的意思是:params 的类型是 T,它必须是 Params 的特定版本。然后作为返回类型,你可以从 T 中取出键,以找到在 Potato 上对应的类型。

最后,你需要实际的函数实现,这些实现应考虑到这三种情况:

function getPotato(arg?: keyof Potato | Params): Potato | Potato[keyof Potato] {
    if (arg === undefined) {
        return ... // first overload
    }

    if (typeof arg === 'string') {
        return ... // second overload
    }

    const { thing, thing2, key } = arg
    return ... // third overload
};

把所有内容结合在一起,这给我们以下代码:

type Banana = {
    id: string;
    name: string;
}

type Potato = {
    id: string;
    name: string;
    phone: number;
    monkey: Banana;
}

type Params = {
    thing: number;
    thing2: number;
    key: keyof Potato;
}

function getPotato(): Potato
function getPotato<K extends keyof Potato>(key: K): Potato[K]
function getPotato<T extends Params>(params: T): Potato[T['key']]
function getPotato(arg?: keyof Potato | Params): Potato | Potato[keyof Potato] {
    // ... 
};

我用以下赋值测试了这些类型:

const a: Potato = getPotato()
const b: string = getPotato('id')
const c: number = getPotato({ thing: 2, thing2: 1, key: 'phone' })
const d: Banana = getPotato('monkey')