您遇到了 TypeScript 的设计限制。请参阅microsoft/TypeScript#38872 了解更多信息。
问题是你给了selector 和render 属性回调,其参数(data 和test)没有明确的类型注释。因此他们是contextually typed;编译器需要为这些参数推断类型,并且不能直接使用它们来推断其他类型。编译器推迟了这一点,并尝试从它当前知道的内容中推断出D 和S。它可以将D 推断为{test: boolean},因为data 属性属于这种类型。但它不知道将S 推断为什么,并且默认为unknown。此时,编译器可以开始执行上下文类型:selector 回调的data 参数现在已知是{test: boolean} 类型,但是render 回调的test 参数被赋予了类型unknown。至此,类型推断结束,你就卡住了。
根据 TypeScript 首席架构师 comment 的说法:
为了支持这种特殊场景,我们需要额外的推理阶段,即每个上下文敏感属性值一个,类似于我们对多个上下文敏感参数所做的事情。不清楚我们是否想在那里冒险,而且它仍然对写入对象文字成员的顺序很敏感。归根结底,如果没有完全统一类型推断,我们可以做的事情是有限度的。
那么可以做些什么呢?问题在于上下文回调参数类型和通用参数推断之间的相互作用。如果您愿意放弃其中之一,您可以切断类型推断的 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
));
或者,更详细但可能更容易理解,使用fluentbuilder 模式:
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 link to code