打字稿无法识别条件

时间:2021-03-20 17:28:36

标签: typescript typescript-generics

我有以下代码:

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 类型。 但我有 TypeScript 错误:

<块引用>

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

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

2 个答案:

答案 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 来保护该值。

Typescript Playground Link

作为旁注,我很惊讶您将 undefined 的默认值分配给参数 params: D['params'] 时没有出现任何错误,因为特定的 D 类型可能不允许 { {1}}。

答案 1 :(得分:2)

长期以来,TypeScript 的编译器根本不会尝试使用 control flow analysis 来缩小类型依赖于尚未指定的泛型类型参数的值的类型。在您的情况下,检查 params !== undefined 不会将 paramsD["params"] 缩小到其他内容。由于您陈述的原因,这令人沮丧:您刚刚检查了 paramsundefined,为什么编译器不明白?这是 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 版本中按原样运行上述代码,它将正常工作。

观察: Playground link to code

有效!


我无法说明这个问题的时间安排是多么巧合。问题 microsoft/TypeScript#13995 已经开放了 四年,并于昨天得到修复。当然,TypeScript 4.3 to be released 需要一点时间,因此您可能无法立即利用此修复程序。但至少你知道它很快就会到来!