TypeScript - 类型 'boolean' 不满足 returns 布尔值类型的约束

TypeScript - Type 'boolean' does not satisfy the constraint of a type that returns a boolean

我有一个管理对象缓存的 TypeScript class。它有一个接受项目 ID 的 get 方法,以及一个可选的布尔值,用于确定是否应调用 API 来获取尚未缓存的对象。如果布尔参数是 falseundefined,该方法应该 return CachedObject | undefined。否则,它应该 return Promise<CachedObject | undefined>。但是,布尔参数只有在 Cache class 的 FetchObject 类型参数为 true:

时才可用
class ExampleClass {};
type CachedObject = ExampleClass;

type GetFetch<FetchObject> = FetchObject extends true ? boolean : false;

type Result<CachedObject, Fetch> = Fetch extends true ? Promise<CachedObject | undefined> : (CachedObject | undefined);

export default class Cache<FetchObject = true> {

    get<Fetch extends GetFetch<FetchObject> = false>(id: string, fetch?: Fetch): Result<CachedObject, Fetch> {

        //
        return;
    }
}

new Cache<false>().get("123"); // ExampleClass | undefined
new Cache().get("123"); // ExampleClass | undefined
new Cache().get("123", true); // Promise<ExampleClass | undefined>

然而,第 10 行(get<...>(...))对于 = false 默认类型参数有以下错误:

Type 'boolean' does not satisfy the constraint 'GetFetch<FetchObject>'

我发现如果 FetchObject 是在没有 class 的情况下明确定义的:

type FetchObject = true;

export default function get<Fetch extends GetFetch<FetchObject> = false>(id: string, fetch?: Fetch): Result<CachedObject, Fetch> {

    //
    return;
}

没有任何错误,get函数可以正常使用。为什么这不能用作 class 的类型参数?

这个很棘手。我尽力解释发生了什么。除非必须(出于性能原因),否则 Typescript 不会完全评估泛型。 GetFetch<FetchObject> 只包含可以是任何东西的泛型,所以编译器还没有评估。如果您已经像使用 type FetchObject = true; 那样输入了一些真实类型,这将有所不同。当您想将 Fetch 的默认值设置为 false 时,问题就出现了。因为 GetFetch<FetchObject> 还没有完全计算,编译器不知道表达式 FetchObject extends true ? boolean : false; 只能是布尔值。无论编译器认为 GetFetch<FetchObject> 的计算结果是什么,我们都可以肯定它不是 boolean。这个问题的解决方案是要么强制编译器相信表达式只能是布尔值,要么将表达式移动到必须对其进行全面评估的位置。我认为解决此问题的最佳方法是将评估移至函数结果,如 this:

class ExampleClass {};
type CachedObject = ExampleClass;

type Result<CachedObject, Fetch> = Fetch extends true ? Promise<CachedObject | undefined> : (CachedObject | undefined);

// prevent fetching, FetchObject= true
type CanBeFetched<FetchedObject, Fetch> = FetchedObject extends true ? Fetch : never;

export default class Cache<FetchObject = true> {

    get<Fetch extends boolean = false>(id: string, fetch?: CanBeFetched<FetchObject, Fetch>): Result<CachedObject, Fetch> {
        // do something
        return null as any;
    }
}

new Cache<false>().get("123"); // ExampleClass | undefined
new Cache<false>().get("123", true); // Error, can't be used when FetchObject = false.
new Cache().get("123"); // ExampleClass | undefined
new Cache().get("123", true); // Promise<ExampleClass | undefined>
new Cache().get("123", false); // ExampleClass | undefined