Typescript无法识别条件



我有以下代码:

type HTTPGet = {
url: string
params?: Record<string, unknown>
result: unknown
}
export const httpGet = <D extends HTTPGet>(url: D['url'], params: D['params'] = undefined): Promise<D['result']> => {
let path = url
if (params !== undefined) {
path += '?' + toHTTPQueryString(params);
}
return fetch(path, {
method: 'GET',
credentials: 'include',
headers: {
'Accept': 'application/json'
}
})
}

在条件params !== undefined之后,我调用HTTPQueryString,它只接受Record<字符串,未知>类型但我有TypeScript错误:

TS2345:类型为"D[quot;params"]"的参数不可分配给类型为"Record<字符串,未知>'。键入"记录<字符串,未知>|undefined"不可分配给类型"Record<字符串,未知>'。类型"undefined"不可分配给类型"Record<字符串,未知>'。

为什么收到此消息?TypeScript应该理解我有条件params !== undefined,并且只保留Record<string, unknown>类型,即可分配给toHTTPQueryString参数类型。

这是一个有趣的例子。这显然是一个";错误";在Typescript方面,以正确地细化类型。

错误归结为typescript如何评估类型保护。考虑这两个用户定义的类型保护。它们都进行相同的检查,但对签名的定义不同。

const isDefined1 = <T extends any>(value: T | undefined): value is T => { 
return value !== undefined;
}
const isDefined2 = <T extends any>(value: T): value is Exclude<T, undefined> => { 
return value !== undefined;
}
if (isDefined1(params)) {
path += '?' + toHTTPQueryString(params); // error
}
if (isDefined2(params)) {
path += '?' + toHTTPQueryString(params); // no error
}

CCD_ 5表示";CCD_ 6的类型现在排除了CCD_;。这个有效。

isDefined1最接近于Typescript对自动类型保护params !== undefined所做的操作。上面写着";如果value的类型是与undefined的并集,则从并集中删除undefined"这个不起作用,并且会出现与以前相同的错误。类型仍然是D['params'],其中仍然包括undefined

它失败了,因为D是泛型,所以D['params']的实际类型未知。我们知道它是extends Record<string, unknown> | undefined,但我们不知道它到底是什么。所以Typescript并没有完全评估它。它不将其视为并集,并删除undefined,因为实际类型可能不是并集。

如果您的函数将没有泛型的params参数定义为Record<string, unknown> | undefined,那么这两个函数都可以工作,原始的params !== undefined也可以工作。

但就目前情况来看,您可以使用isDefined2来保护价值。

打字游戏场链接

顺便说一句,我很惊讶您在为参数params: D['params']分配默认值undefined时没有遇到任何错误,因为特定的D类型可能不允许undefined

在最长的一段时间里,TypeScript的编译器根本不会尝试使用控制流分析来缩小值的类型,该值的类型依赖于尚未指定的泛型类型参数。在您的情况下,检查params !== undefined不会将paramsD["params"]缩小到其他内容。这很令人沮丧,因为你说:你刚刚检查了params中的undefined,为什么编译器不理解?这是GitHub中一个长期存在的问题的主题:microsoft/TypeScript#13995。

在昨天之前,我会说";事情就是这样,对不起"并提供了如下解决方法:您可以将泛型类型D['params']的值扩大到其特定约束类型HTTPGet['params'],然后检查:

const httpGet = <D extends HTTPGet>(url: D['url'],
_params: D['params'] = undefined): Promise<D['result']> => {
let path = url
const params: HTTPGet['params'] = _params; // widen to specific type here
if (params !== undefined) {
path += '?' + toHTTPQueryString(params); // okay now
}
// ...

但是。。。microsoft/TypeScript#13395刚刚被拉取请求"microsoft/TypeScript#43183">修复;改进控制流分析中通用类型的缩小";。在TypeScript 4.3及更高版本中,您可以预期在某些情况下(您可以在拉取请求注释中阅读如何操作(,像D["params"]这样的泛型类型的值将自动扩展到其特定约束,以允许进行控制流分析。

意思是:如果在合并拉请求后,在TypeScript版本中按原样运行上述代码,它就会正常工作。

观察:到代码的游乐场链接

它有效!


我无法说明这个问题的时间有多巧合。这个问题,microsoft/TypeScript#13995,已经开放了四年,昨天得到了修复。当然,TypeScript 4.3的发布还需要一段时间,所以您可能无法立即利用修复。但至少你知道它很快就会到来!

最新更新