您遇到了 TypeScript 的设计限制。有关更多信息,请参阅microsoft/TypeScript#38872。
问题是您提供了selector
和render
属性回调,其参数 (data
和test
) 没有明确的类型注释。因此它们是上下文类型的;编译器需要为这些参数推断类型,不能直接使用它们来推断其他类型。编译器拥有过就这个问题和试图推断D
,并S
从它目前知道。它可以推断D
为{test: boolean}
因为该data
属性属于这种类型。但它不知道推断什么S
,它默认为unknown
。此时,编译器可以开始执行上下文类型:在data
该参数selector
的回调,现在是已知的类型{test: boolean}
,但test
的参数render
回调被赋予类型unknown
。在这一点上,类型推断结束,你被卡住了。
根据TypeScript 首席架构师的评论:
为了支持这一特定场景,我们需要额外的推理阶段,即每个上下文敏感属性值一个,类似于我们对多个上下文敏感参数所做的。不清楚我们想在那里冒险,并且它仍然对编写对象文字成员的顺序很敏感。最终,如果类型推断没有完全统一,我们可以做的事情是有限的。
那么可以做什么呢?问题在于上下文回调参数类型和通用参数推断之间的相互作用。如果你愿意放弃其中之一,你可以切断类型推断的 Gordian Knot。例如,您可以通过手动注释一些回调参数类型来放弃一些上下文回调参数类型:
Component({
data: { test: true },
selector: (data: { test: boolean }) => data.test, // annotate here
render: (test) => test, // test is boolean
})
或者,您可以通过手动指定泛型类型参数来放弃泛型参数类型推断:
Component<{ test: boolean }, boolean>({ // specify here
data: { test: true },
selector: (data) => data.test,
render: (test) => test, // test is boolean
})
或者,如果您不愿意这样做,也许您可以Props<D, S>
分阶段创建您的值,其中每个阶段只需要一点点类型推断。例如,您可以用函数参数替换属性值(请参阅上面的引用“类似于我们对多个上下文敏感参数所做的操作”):
const makeProps = <D, S>(
data: D, selector: (data: D) => S, render: (data: S) => any
): Props<D, S> => ({ data, selector, render });
Component(makeProps(
{ test: true },
(data) => data.test,
(test) => test // boolean
));
或者,更详细但可能更容易理解,使用流畅的 构建器模式:
const PropsBuilder = {
data: <D,>(data: D) => ({
selector: <S,>(selector: (data: D) => S) => ({
render: (render: (data: S) => any): Props<D, S> => ({
data, selector, render
})
})
})
}
Component(PropsBuilder
.data({ test: true })
.selector((data) => data.test)
.render((test) => test) // boolean
);
在 TypeScript 的类型推断功能不足的情况下,我倾向于更喜欢构建器模式,但这取决于您。
Playground 链接到代码