我有以下代码:
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
之后我调用 toHTTPQueryString,它只接受 Record
TS2345:“D["params"]”类型的参数不可分配给“Record
为什么我会收到这条消息? TypeScript 应该明白我有条件 params !== undefined
并且只保留 Record<string, unknown>
类型,可以分配给 toHTTPQueryString
参数类型。
答案 0 :(得分:3)
这是一个有趣的。显然,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
}
isDefined2
表示“value
的类型现在不包括 undefined
”。这个有效。
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
来保护该值。
作为旁注,我很惊讶您将 undefined
的默认值分配给参数 params: D['params']
时没有出现任何错误,因为特定的 D
类型可能不允许 { {1}}。
答案 1 :(得分:2)
长期以来,TypeScript 的编译器根本不会尝试使用 control flow analysis 来缩小类型依赖于尚未指定的泛型类型参数的值的类型。在您的情况下,检查 params !== undefined
不会将 params
从 D["params"]
缩小到其他内容。由于您陈述的原因,这令人沮丧:您刚刚检查了 params
的 undefined
,为什么编译器不明白?这是 GitHub 中一个长期存在的问题的主题:microsoft/TypeScript#13995。
在昨天之前,我会说“就是这样,抱歉”并提供了如下解决方法:您可以将泛型类型 D['params']
的值扩大到其特定的约束类型 {{1 }} 并检查:
HTTPGet['params']
但是... microsoft/TypeScript#13395 刚刚被拉取请求 microsoft/TypeScript#43183,“改进控制流分析中泛型类型的缩小”修复。在 TypeScript 4.3 及更高版本中,您可以预期在某些情况下(您可以在拉取请求说明中阅读操作方法),诸如 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
}
// ...
之类的泛型类型的值将自动扩大到其特定约束,以允许进行控制流分析.
含义:如果您在合并拉取请求后在 TypeScript 版本中按原样运行上述代码,它将正常工作。
有效!
我无法说明这个问题的时间安排是多么巧合。问题 microsoft/TypeScript#13995 已经开放了 四年,并于昨天得到修复。当然,TypeScript 4.3 to be released 需要一点时间,因此您可能无法立即利用此修复程序。但至少你知道它很快就会到来!