我有以下代码:

function inferTest<T>(factory: (setter: (setterFn: (arg: NoInfer<T>) => NoInfer<T>) => void) => T) {
    return factory(null!);
}
const inferred = inferTest((setter) => ({
    count: 0,
    increment: () => setter(state => ({ count: state.count + 1 })) // 'state' is of type 'unknown'.
}));
console.log(inferred.count); // 'inferred' is of type 'unknown'.

我希望编译器将类型T推断为

{
    count: number,
    increment: () => void,
}

然而,由于返回类型factory中存在increment属性,因此编译器无法推断类型T,而是将unknown指定为类型.这会导致两个编译器错误:

'state' is of type 'unknown'.
'inferred' is of type 'unknown'.

如果您删除increment属性,那么它可以正常工作:

function inferTest<T>(factory: (setter: (setterFn: (arg: NoInfer<T>) => NoInfer<T>) => void) => T) {
    return factory(null!);
}
const inferred = inferTest(() => ({
    count: 0
}));
console.log(inferred.count);

我很好奇为什么在factory的定义中使用setter参数会导致编译器放弃推理,特别是考虑到除了T的一个外观之外的所有外观上都使用了NoInfer类型.有人知道这里发生了什么吗?

推荐答案

The NoInfer<T> utility type在这里没有做任何事情,也不是为了这个目的.如果您编写generic函数,而TypScript从您不希望的位置推断类型参数,则可以使用NoInfer来阻止该推断. 但在以下代码中:

function inferTest<T>(factory: (setter: (setterFn: (arg: T) => T) => void) => T) {
    return factory(null!);
}
const inferred = inferTest((setter) => ({
    count: 0,
    increment: () => setter(state => ({ count: state.count + 1 })) 
}));
// function inferTest<unknown>(
//   factory: (setter: (setterFn: (arg: unknown) => unknown) => void) => unknown
// ): unknown

T的推断完全失败,类型参数回到the unknown type. 添加NoInfer不会改变或改进这一点.


推理失败的原因是因为你的回调是context-dependent.你没有annotated setterstate.TypScript倾向于推迟对上下文相关函数的推断,直到它知道通用类型参数之后.但当然,类型参数取决于函数的类型.TypScript将此视为循环且无法继续.您希望编译器只能看到返回类型的count部分,而无需了解increment部分.但它实际上并不是这样工作的; TypScript不知道如何执行microsoft/TypeScript#30134中描述的"完整unification".相反,它使用经验主义驱动的算法,该算法适用于多种情况,尤其适用于半写代码.

microsoft/TypeScript#47599中描述了TypScript无法推断似乎相互依赖的上下文类型和通用类型参数的一般问题. 这里已经进行了改进,例如在microsoft/TypeScript#48538中实现的改进.但仍然存在并且将永远存在未经证实的案件.我怀疑你的原因不像写的那样工作是因为你有嵌套的上下文回调.

也许您的代码会在TypScript的future 版本中原样得到支持.与此同时,这里令人失望的一般建议是,当推理不能按照您的意愿进行时,请手动注释并指定类型.或者重构您的代码,以便推断可以以非循环的方式发生(有时人们可以创建构建器,以便他们可以增量地构建它,而不是单个自引用对象).对于您的代码,最好只定义{ count: number, increment: () => void }的类型并手动指定T作为该类型:

interface MyType {
  count: number;
  increment(): void;
}
inferTest<MyType>((setter) => ({
  count: 0,
  increment: () => setter(state => (
    {
      count: state.count + 1,
      increment() { } // <-- you forgot this
    }))
}));

无论如何,NoInfer<T>对这种情况没有任何帮助.它所做的只是阻止不必要的推理,这可能会导致其他推理发生.但如果没有推论,那么就没有什么可以阻止的.没有PleaseInfer<T>类型(目前尚不清楚如何以实际处理Microsoft/Typescript#47599的方式实现这样的事情,而不是只是在那里失败).

Playground link to code

Typescript相关问答推荐

TypScript中的算法运算式

处理API响应中的日期对象

node—redis:如何在redis timeSeries上查询argmin?

在TypeScript中使用泛型的灵活返回值类型

在将对象从一个对象转换成另一个对象时,可以缩小对象的键吗?

webpack错误:模块找不到.当使用我的其他库时,

在嵌套属性中使用Required

在分配给类型的只读变量中维护const的类型

垫表页脚角v15

下载量怎么会超过下载量?

如何在具有嵌套数组的接口中允许新属性

设置不允许相同类型的多个参数的线性规则

如何在TypeScrip中使用数组作为字符串的封闭列表?

Typescript不允许在数组中使用联合类型

如何在TypeScript中描述和实现包含特定属性的函数?

如何创建允许使用带有联合类型参数的Array.from()的TS函数?

在类型{}上找不到带有string类型参数的索引签名 - TypeScript

使用本机 Promise 覆盖 WinJS Promise 作为 Chrome 扩展内容脚本?

如果父级被删除或删除,如何自动删除子级?

如何在 ngFor 循环中以Angular 正确动态渲染组件