【问题标题】:Type Property Relying on Return Type of Another Property类型属性依赖于另一个属性的返回类型
【发布时间】:2021-04-02 11:54:01
【问题描述】:

我正在尝试将一个对象传递给一个函数(在本例中为一个 React 组件的 props)。

此对象包含以下属性:

  • data - 一些任意数据
  • selector - 将返回部分数据的函数
  • render - 处理渲染选定数据的函数 (JSX)

我不确定如何正确输入。

我最初认为可以这样做:

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 属性,我们会得到正确的返回类型(布尔值)。

我也试过了:

  • 使用通用参数S extends (data: D) =&gt; unknownrender 的数据类型为ReturnType&lt;S&gt;
  • 使用单独的类型推断和提取selector 的返回类型,使用type Extract&lt;D, S&gt; = S extends (data: D) =&gt; infer T ? T : D

【问题讨论】:

  • 有趣的是,它可以像您期望的那样与每个项目一起工作,因为它是一个简单函数中的自己的参数:tsplay.dev/qWJ6VW

标签: reactjs typescript


【解决方案1】:

这里有一个解决方案:

import React from 'react'

type Props<D, S> = {
  data: D
  selector: (data: D) => S
  render: (data: S) => any
}


const Comp = <D, S>(props: Props<D, S>) => null

const result = <Comp<number, string> data={2} selector={(data: number) => 'fg'} render={(data: string) => 42} /> // ok
const result2 = <Comp<number, string> data={2} selector={(data: string) => 'fg'} render={(data: string) => 42} /> // expected error
const result3 = <Comp<number, string> data={2} selector={(data: number) => 'fg'} render={(data: number) => 42} /> // expected error

你应该明确定义组件的泛型

【讨论】:

  • 我应该补充一点,我的目标是完全通过推理来做到这一点,因为我使用的实际类型有点失控(生成的 GraphQL 查询类型)。尝试为其中之一指定选择器返回类型可能有点混乱。
【解决方案2】:

您遇到了 TypeScript 的设计限制。请参阅microsoft/TypeScript#38872 了解更多信息。

问题是你给了selectorrender 属性回调,其参数(datatest)没有明确的类型注释。因此他们是contextually typed;编译器需要为这些参数推断类型,并且不能直接使用它们来推断其他类型。编译器推迟了这一点,并尝试从它当前知道的内容中推断出DS。它可以将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&lt;D, S&gt; 值,每个阶段只需要一点类型推断。例如,您可以用函数参数替换属性值(参见上面的引用“类似于我们对多个上下文敏感参数所做的”):

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

【讨论】:

  • 构建器模式不适用于 React 道具,尽管您正在寻找格式 &lt;Component data={{ test: true }} selector={(data) =&gt; data.test} render={(test) =&gt; test} /&gt;
  • 感谢您提供的信息丰富的回答!我认为可能是这种情况。等我回去工作后,我将不得不考虑你的第三个建议。
猜你喜欢
  • 1970-01-01
  • 2017-03-23
  • 1970-01-01
  • 2022-11-30
  • 2022-12-19
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多