使用 recompose 和 typescript 正确键入 HigherOrderComponents

Posted

技术标签:

【中文标题】使用 recompose 和 typescript 正确键入 HigherOrderComponents【英文标题】:Correct Typing for HigherOrderComponents with recompose and typescript 【发布时间】:2019-05-30 05:09:08 【问题描述】:

我目前正在尝试重新组合到我的反应代码库中。因此,我试图让一些基本的东西正常工作,并且我得到了它的工作,但我不确定这是否是 recompose 的正确工作方式。

所以我有以下代码:

interface Value 
  value: string


interface Loading 
  loading: boolean


const TestComponent = (props: Value) => <div>Value: props.value</div>
const LoadingComponent = () => <div>Loading ...</div>

所以我有一个 TestComponent,它应该显示道具中提供的值,另外我还有一个 LoadingComponent,它应该在设置加载道具时显示。

所以我使用了recompose的branch函数

const withLoading = branch<Loading>(
  (loading) => loading, 
  renderComponent(LoadingComponent)
) 

现在,当我在 任何没有道具的组件上使用 withLoading时,我可以在它们上设置加载道具。

const EnhancedCompWithLoading = withLoading(SomeCompWithoutProps)

render() 
  return <EnhancedCompWithLoading loading=this.state.loading />

这对我来说很好用,真正的问题是在尝试将其与带有道具的组件一起使用时开始。当我这样尝试时:

const TestWithLoading = withLoading(TestComponent)

render() 
  return <TestWithLoading value="testVal" loading=this.state.loading />

我收到错误消息TS2339: Property 'value' does not exist on type 'IntrinsicAttributes & IntrinsicClassAttributes<Component<Loading, any, any>> & Readonly< children?: ReactNode; > & Readonly<Loading>'.

所以我查看了@types/recompose 中的类型定义。 branch&lt;TOutter&gt; 返回 ComponentEnhancer&lt;any,TOutter&gt;。据我所知,我希望能够提供any 组件,而&lt;TOutter&gt; 泛型就是这样,生成的组件知道测试功能所需的道具。这也可以在没有额外道具的情况下工作。

但是 ComponentEnhancer 的 TypeDefinition 看起来像这样(重构 0.30.2):

interface ComponentEnhancer<TInner, TOutter> 
  (component: Component<TInner>): ComponentClass<TOutter>

所以,我从之前的branch 函数收到的ComponentEnhancer&lt;any, Loading&gt; 将返回一个ComponentClass&lt;Loading&gt;。但是,我提供给 ComponentEnhancer 的组件的 &lt;TInner&gt; 将被丢弃,并且我无法在增强组件中使用我的 Value 道具。

所以我的问题是,我做错了吗,有没有更好的方法来实现这一点(使用重组)。或者它只是 TypeDeclaration 中的一个错误,因为将 ComponentEnhancer 的返回更改为 ComponentClass&lt;TOutter &amp; TInner&gt; 为我解决了整个问题。 对此有什么想法吗?

【问题讨论】:

【参考方案1】:

我对@9​​87654321@ 和recompose 不熟悉,但如果这只是打字问题,我们可以解决。

问题是branch 的类型不是很好。如果意图是将包装组件的属性转发到 HOC 并将显式键入到 branch 的道具添加到 HOC,那么结果的更好类型是:

type BetterComponentEnhancer<TOutter> = 
    <TInner>(component: React.ComponentType<TInner>): React.ComponentClass<TInner & TOutter>

使用这种类型,这将起作用:

const withLoading = branch<Loading>(
    ( loading ) => loading,
    renderComponent(LoadingComponent)
)as unknown as BetterComponentEnhancer<Loading>

type BetterComponentEnhancer<TOutter> = 
    <TInner>(component: React.ComponentType<TInner>): React.ComponentClass<TInner & TOutter>


const TestWithLoading = withLoading(TestComponent)

function render() 
    return <TestWithLoading value="testVal" loading=true />

【讨论】:

嗯,这基本上就是我在最后两句话中所写的。将返回类型更改为 ComponentClass&lt;TInner &amp; TOutter&gt; 时,它将起作用。但是我对 as unknown 以及为什么 BetterComponentEnhancer 参数前面的 &lt;TInner&gt; 感到困惑 @Azael wops,我的错误错过了问题的最后一行,我将BetterComponentEnhancer 设为通用函数以从传入的组件(在本例中为 TestComponent)@ 987654331@ 只是一个双重类型断言,因为 TS 不允许我直接断言到 BetterComponentEnhancer 所以我尝试了您的解决方案,它确实有效。工具提示显示React.ComponentClass&lt;Value &amp; Loading, any&gt;。对于解决方案,它是React.ComponentClass&lt;any, any&gt;。那里的 TS 语法仍然有点困惑。我将在 @types/recompose 存储库上打开一个问题,并参考您的解决方案。 @Azael 语法只是一个通用函数签名,它是非常标准的 TS。如果您打开问题,也请在此处发布,我很想看看团队的回复 遗憾的是,我仍然没有对该问题的答案......但是我正在使用您的解决方案,所以我将您的答案标记为正确。

以上是关于使用 recompose 和 typescript 正确键入 HigherOrderComponents的主要内容,如果未能解决你的问题,请参考以下文章

如何在 reactjs 中使用 recompose 清理表单?

[Recompose] Lock Props using Recompose -- withProps

你如何从 recompose 中读取这个 curry'd 函数……我的脑痛

使用 recompose setStatic 并将其连接到 redux (nextjs)

[Recompose] Transform Props using Recompose --mapProps

[Recompose] Add Lifecycle Hooks to a Functional Stateless Component using Recompose