类型属性依赖于另一个属性的返回类型 [英] Type Property Relying on Return Type of Another Property

查看:40
本文介绍了类型属性依赖于另一个属性的返回类型的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试将一个对象传递给一个函数(在本例中是将 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 data
  • selector - function which will return some part of the data
  • render - 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 未知.但是,如果我们删除依赖于 Srender 属性,我们会得到正确的返回类型(布尔值).

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, with render's data type being ReturnType<S>.
  • Using a separate type to infer and extract the selector's return type using type 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.

问题是你给了 selectorrender 属性回调,它们的参数(datatest)有没有明确的类型注释.因此,它们是上下文类型;编译器需要为这些参数推断类型,不能直接使用它们来推断其他类型.编译器暂缓这一点,并试图从它当前知道的信息中推断出 DS.它可以将 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆