渲染属性是什么,它与高阶组件有何不同?

16

目前看来渲染属性并没有得到足够的关注,然而它被许多知名的React库广泛使用,例如react-router 4react motion等。React网站也专门为其提供了一个专用的部分。这种模式的出现有什么原因?与通常所知的高阶组件(HOC)模式相比如何?

1个回答

15

请为我的研究留下答案,不同的答案和讨论都非常欢迎!

HOC借鉴了高阶函数的概念:

高阶函数(也称为函数式、函数形式或函数子)是至少执行以下操作之一的函数:

  • 将一个或多个函数作为参数(即过程参数),
  • 将一个函数作为其结果返回。[有争议-讨论]

HOC

高阶组件(HOC)是React中用于重用组件逻辑的高级技术。

源自这个Gist

该模式涉及到静态组合。核心/可重用的逻辑被封装在HOC中,同时将移动部分留给组件。

以react router中的withRouter为例:

每当渲染时,withRouter将更新的match、location和history props传递给包装的组件。

// 这避免了shouldComponentUpdate

withRouter(connect(...)(MyComponent))

现在,您会得到一个增强版的MyComponent,其中包含由路由器HOC传递的props:{ history,match,location,...connectProps,...ownProps }

常见的方法是

compose(
  connect( ... ),
  enhance( ... ),
  withRouter( ... ),
  lifecycle( ... ),
  somethingElse( ... )
)(MyComponent);  

很棒的部分是,你可以使用compose工具无限组合这些高阶组件,以获得最终增强版的组件,你的组件将从HOC返回的新组件中注入redux store、react router等知识。

这种方法的缺点是:

  1. The behavior of the component is defined before runtime thus lost the power of react's rendering lifecycles, say you can't do something like:

    compose(
      this.state.shouldConnect && connect( ... ),
      this.state.shouldEnhance && enhance( ... ),
      this.state.shouldWithRouter && withRouter( ... ),
      ...
    )(MyComponent); 
    

    since state/props is not available before your code runs.

  2. Indirection & Naming collisions.

使用ES6类的HOC与createClass时使用mixin时遇到的问题非常相似,只是稍微重新排列了一下。
由于HOC将组件包装并创建新组件,而不是混合到现有组件中,因此引入了很多仪式感。

渲染属性

渲染属性是组件用来知道如何渲染的函数属性。

最初由react-motion采用,早在Dan's Gist提交redux的几周前就已经出现了。

这种模式涉及到动态组合。核心/可重用逻辑保留在组件中,而移动部分作为回调属性传递。

您可以通过渲染属性创建高阶组件。

仍然以withRouter为例:

const withRouter = Component => {
  const C = props => {
    const { wrappedComponentRef, ...remainingProps } = props;
    return (
      <Route
        render={routeComponentProps => (
          <Component
            {...remainingProps}
            {...routeComponentProps}
            ref={wrappedComponentRef}
          />
        )}
      />
    );
  };
  ...
  return hoistStatics(C, Component);
}; 

而反之则不成立。

<Connect render={connectPropsMergedWithState => {
  <Enhance render={enhancePropsMergedWithState => {
    <WithRouter render={routerPropsMergedWithState => {
      <Lifecycle render={lifecyclePropsMergedWithState => {
        <SomethingElse render={somethingElsePropsMergedWithState => {
          ...
        }/>
        ...
      }/>
      ...
    }/>
    ...
  }/>
  ...
}/>

虽然它看起来不太好,但有很多好处。

  1. 它更明确,因为我们可以看到传递给渲染属性的参数是什么。
  2. 由于第一点,它可以避免潜在的属性冲突。
  3. 它是动态的,我们可以传递任何我们喜欢的东西(包括状态/属性)到渲染属性。

众所周知的缺点是性能优化很棘手,因为接收哪些属性被推迟到运行时。也许不要做过早的优化可能不是一个好主意,但那可能完全是另一个话题。

如果您同意从react router 3转移到4的方向移动,渲染属性可能是您的选择。

参考文献:

  1. https://cdb.reacttraining.com/use-a-render-prop-50de598f11ce
  2. https://reactrocket.com/post/turn-your-hocs-into-render-prop-components

我真的很震惊,a)通常过度热衷的版主没有标记这个问题过于宽泛,b)这篇优秀的写作却没有任何赞。编辑:啊,我刚刚注意到你自己写了问题并回答了它;为什么不写一篇博客文章呢? - Parker Ault
1
@ParkerAult 如果这个答案能有所帮助,那就太好了。正如你所猜测的那样,我几天前也有同样的问题,但它被标记为过于宽泛。由于我的初衷是寻找答案,我进行了一些研究并将其留在这里以获得更好的答案。我仍然认为这是一个合理的问题,可以从不同的人那里得到很多好的想法 :) - Allen
我认为这是一个合理的问题,因为它似乎是一种越来越受欢迎的设计模式。如果我说“渲染属性”模式与“函数作为子组件”相同,是否正确?因为子组件充当常规属性,可以传递任何数据(在这种情况下是函数),因此可以像渲染属性一样调用。 - tomhughes
1
@tomhughes 当然,它们是完全相同的东西。更重要的是,正如你所说,它们实际上是具有React糖的非常通用的设计模式。HOC只是类似于Decorator/mixin模式的高阶函数,而渲染属性(child as function)则是提供控制反转给组件客户端的回调函数。 - Allen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接