类型属性依赖于另一个属性的返回类型 [英] Type Property Relying on Return Type of Another Property
问题描述
我正在尝试将一个对象传递给一个函数(在本例中是将 props 传递给 React 组件).
I'm trying to pass an object to a function (in this case props to a React component).
该对象包含以下属性:
data
- 一些任意数据selector
- 返回部分数据的函数render
- 处理渲染选定数据 (JSX) 的函数
data
- some arbitrary dataselector
- function which will return some part of the datarender
- function which will handle rendering the selected data (JSX)
我不确定如何正确输入.
I'm unsure how to type this out correctly.
我最初认为可以通过这种方式完成:
I initially assumed it could be done this way:
type Props<D, S> = {
data: D
selector: (data: D) => S
render: (data: S) => any
}
const Component = <D, S>(props: Props<D, S>) => null
Component({
data: { test: true },
selector: (data) => data.test,
render: (test) => test, // test is unknown
})
这导致通用 S
未知.但是,如果我们删除依赖于 S
的 render
属性,我们会得到正确的返回类型(布尔值).
This results in the generic, S
, being unknown.
However if we remove the render
property which relies on S
we get the correct return type (boolean).
我也试过:
- 使用通用参数,
S extends (data: D) =>未知
,render
的数据类型为ReturnType
. - 使用单独的类型来推断和提取
selector
的返回类型,使用type Extract
.= S扩展(数据:D)=>推断 T ?T : D
- Using a generic parameter,
S extends (data: D) => unknown
, withrender
's data type beingReturnType<S>
. - Using a separate type to infer and extract the
selector
's return type usingtype Extract<D, S> = S extends (data: D) => infer T ? T : D
.
推荐答案
您遇到了 TypeScript 的设计限制.有关详细信息,请参阅 microsoft/TypeScript#38872.
You've run into a design limitation of TypeScript. See microsoft/TypeScript#38872 for more information.
问题是你给了 selector
和 render
属性回调,它们的参数(data
和 test
)有没有明确的类型注释.因此,它们是上下文类型;编译器需要为这些参数推断类型,不能直接使用它们来推断其他类型.编译器暂缓这一点,并试图从它当前知道的信息中推断出 D
和 S
.它可以将 D
推断为 {test: boolean}
因为 data
属性属于这种类型.但它不知道将 S
推断为什么,它默认为 unknown
.此时,编译器可以开始执行上下文类型:selector
回调的 data
参数现在已知类型为 {test: boolean}
,但 render
回调的 test
参数的类型为 unknown
.到这里,类型推断结束,你就卡住了.
The problem is that you gave selector
and render
property callbacks whose parameters (data
and test
) had no explicit type annotation. Thus they are contextually typed; the compiler needs to infer types for these parameters and cannot use them directly to infer other types. The compiler holds off on this and tries to infer D
and S
from what it currently knows. It can infer D
as {test: boolean}
because the data
property is of this type. But it has no idea what to infer S
as, and it defaults to unknown
. At this point the compiler can begin to perform contextual typing: the data
parameter of the selector
callback is now known to be of type {test: boolean}
, but the test
parameter of the render
callback is given the type unknown
. At this point, type inference ends, and you're stuck.
根据 TypeScript 首席架构师的评论:
According to a comment by the lead architect of TypeScript:
为了支持这一特定场景,我们需要额外的推理阶段,即每个上下文敏感属性值一个,类似于我们对多个上下文敏感参数所做的.不清楚我们想在那里冒险,它仍然对编写对象文字成员的顺序很敏感.最终,如果类型推断没有完全统一,我们能做的事情就会受到限制.
In order to support this particular scenario we'd need additional inference phases, i.e. one per contextually sensitive property value, similar to what we do for multiple contextually sensitive parameters. Not clear that we want to venture there, and it would still be sensitive to the order in which object literal members are written. Ultimately there are limits to what we can do without full unification in type inference.
那么有什么办法呢?问题在于上下文回调参数类型和通用参数推断之间的相互作用.如果你愿意放弃其中之一,你可以切断类型推断的戈迪安结.例如,您可以通过手动注释一些回调参数类型来放弃一些上下文回调参数类型:
So what can be done? The problem is the interplay between contextual callback parameter types and generic parameter inference. If you're willing to give one of those up, you can cut the Gordian Knot of type inference. For example, you can give up some contextual callback parameter typing by manually annotating some callback parameter types:
Component({
data: { test: true },
selector: (data: { test: boolean }) => data.test, // annotate here
render: (test) => test, // test is boolean
})
或者,您可以通过手动指定泛型类型参数来放弃泛型参数类型推断:
Or, you can give up on generic parameter type inference by manually specifying your generic type parameters:
Component<{ test: boolean }, boolean>({ // specify here
data: { test: true },
selector: (data) => data.test,
render: (test) => test, // test is boolean
})
或者,如果您不愿意这样做,也许您可以分阶段创建 Props<D, S>
值,其中每个阶段只需要一点点类型推断.例如,您可以用函数参数替换属性值(请参阅上面的引用类似于我们对多个上下文敏感参数所做的操作"):
Or, if you're not willing to do that, maybe you can create your Props<D, S>
values in stages where each stage only requires a little bit of type inference. For example, you can replace property values with function parameters (see above quote "similar to what we do for multiple contextually sensitive parameters"):
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
));
或者,更详细但可能更容易理解,使用fluent builder 模式:
Or, more verbosely but possibly more understandable, use afluent builder pattern:
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 的类型推断功能不足的情况下,我倾向于更喜欢构建器模式,但这取决于您.
I tend to prefer builder patterns in cases where TypeScript's type inference capabilities fall short, but it's up to you.
这篇关于类型属性依赖于另一个属性的返回类型的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!