首页 > 解决方案 > Typescript中错误的自动返回类型推导

问题描述

在设置类中,我有一个获取值的方法,如果找不到给定的键,则返回一个可选的默认值:

    /**
     * Returns the value stored at the given key, which can have the form `qualifier.subKey`.
     *
     * @param key The key to look up.
     * @param defaultValue An optional value to be returned if the there's no value at the given key or the key doesn't
     *                     exist at all.
     *
     * @returns If a return value is given the return type is the same as that of the default value. Otherwise it's
     *          either undefined or the same as the type found at the given key.
     */
    public get<T>(key: string, defaultValue: T): T;
    public get(key: string): any;
    public get<T>(key: string, defaultValue?: T): T | undefined {
        const { target, subKey } = this.objectForKey(key, false);
        if (!target || !subKey) {
            return defaultValue;
        }

        return target[subKey] as T ?? defaultValue;
    }

这个实现给了我没想到的返回类型。考虑这个调用:

const removeIdleTime = settings.get("workers.removeIdleTime", 60);

该变量removeIdleTime不是我所期望的 number 类型,而是 type 60。我可以明确地将<number>其用作模板/通用参数,get然后结果就可以了,但是让 Typescript 推断出正确的类型会更酷。必须改变什么才能做到这一点?

更新

我刚刚在 Typescript 中找到了关于类型扩展的描述(在写这个问题时我不知道正确的术语)。事实证明,当将结果分配给get可变变量时,类型被扩大了。否则,它保持为文字类型。

虽然这是有趣的信息,但它对这个问题没有帮助,因为如果在初始分配后没有更改,linter 通常会将任何转换let为​​。const

标签: typescript

解决方案


解决方案

感谢@Etheryte,我找到了一个解决方案:有一种方法可以通过使用条件类型来强制类型扩展。

export type ValueType<T> = T extends string
    ? string
    : T extends number
        ? number
        : T extends boolean
            ? boolean
            : T extends undefined
                ? undefined
                : [T] extends [any]
                    ? T
                    : object;

可以这样使用(注意唯一的变化,返回类型):

    /**
     * Returns the value stored at the given key, which can have the form `qualifier.subKey`.
     *
     * @param key The key to look up.
     * @param defaultValue An optional value to be returned if the there's no value at the given key or the key doesn't
     *                     exist at all.
     *
     * @returns If a return value is given the return type is the same as that of the default value. Otherwise it's
     *          either undefined or the same as the type found at the given key.
     */
    public get<T>(key: string, defaultValue: T): ValueType<T>;
    public get(key: string): any;
    public get<T>(key: string, defaultValue?: T): T | undefined {
        const { target, subKey } = this.objectForKey(key, false);
        if (!target || !subKey) {
            return defaultValue;
        }

        return target[subKey] as T ?? defaultValue;
    }

这也适用于枚举,其中条件类型返回数字或字符串,具体取决于枚举的基本类型。


上一个答案

此行为是设计使然。分配给常量目标的文字值保持其文字类型。这遵循始终存储最窄类型的原则。在可以更改值的情况下(例如,通过将文字分配给可变变量),Typescript 转译器会扩大类型以允许初始文字以外的其他值。您可以在 TypeScript 中的 Literal Type Widening一文中了解更多相关信息。

没有办法(我知道)强制类型扩大,所以我在这里看到了 3 种可能的方法:

  1. get分配调用结果时使用可变目标。这可能是有问题的,因为如果没有其他赋值,linter 将尝试“优化”变量以使其变为不可变。

  2. 向目标添加显式类型注释,例如:

const removeIdleTime: number = settings.get("workers.removeIdleTime", 60);

  1. 明确指定泛型参数:

const removeIdleTime = settings.get<number>("workers.removeIdleTime", 60);

或者

const removeIdleTime = settings.get("workers.removeIdleTime", 60 as number);

不过,所有这些方法都不能真正解决我的问题。


推荐阅读