【问题标题】:React functional stateless component, PureComponent, Component; what are the differences and when should we use what?React 函数式无状态组件,PureComponent,Component;有什么区别,我们什么时候应该使用什么?
【发布时间】:2017-04-03 20:59:10
【问题描述】:

了解到,从 React v15.3.0 开始,我们有一个名为 PureComponent 的新基类,可以使用内置的 PureRenderMixin 进行扩展。我的理解是,在引擎盖下,这对shouldComponentUpdate 内部的道具进行了浅显的比较。

现在我们有 3 种方法来定义 React 组件:

  1. 不扩展任何类的功能性无状态组件
  2. 扩展PureComponent类的组件
  3. 扩展Component 类的普通组件

过去我们曾经将无状态组件称为纯组件,甚至是哑组件。似乎“纯”这个词的整个定义现在在 React 中已经改变了。

虽然我了解这三者之间的基本区别,但我仍然不确定何时选择什么。另外,每种方法的性能影响和权衡是什么?


更新

这些是我希望得到澄清的问题:

  • 我应该选择将我的简单组件定义为功能性(为了简单起见)还是扩展PureComponent 类(为了性能起见)?
  • 我得到的性能提升是真正的权衡吗? 我失去了简单?
  • 当我始终可以使用 PureComponent 以获得更好的性能时,我是否需要扩展普通的 Component 类?

【问题讨论】:

    标签: javascript reactjs ecmascript-6


    【解决方案1】:
    • React.Component 是默认的“正常”组件。您使用class 关键字和extends React.Component 声明它们。将它们视为一个类,具有生命周期方法、事件处理程序和任何方法。

    • React.PureComponent 是一个React.Component,它实现了shouldComponentUpdate(),其函数对其propsstate 进行了浅层比较。如果您知道组件具有更改的道具或状态嵌套数据并且您想要重新渲染,则必须使用forceUpdate()。因此,如果您需要在您作为道具传递或在状态中设置的数组或对象发生更改时重新渲染组件,那么它们就不是很好了。

    • 功能组件是没有生命周期功能的组件。它们应该是无状态的,但它们非常干净整洁,以至于我们现在有了钩子(从 React 16.8 开始),所以你仍然可以拥有状态。所以我猜它们只是“干净的组件”。

    【讨论】:

      【解决方案2】:

      你如何决定,你如何根据我们组件的用途/大小/道具/行为在这三者之间进行选择?

      使用自定义 shouldComponentUpdate 方法从 React.PureComponentReact.Component 扩展会影响性能。使用无状态功能组件是一种“架构”选择,并且没有任何开箱即用的性能优势(目前)。

      • 对于需要轻松重用的简单、仅显示的组件,首选无状态功能组件。通过这种方式,您可以确定它们与实际的应用程序逻辑分离,它们非常容易测试并且它们没有意外的副作用。例外情况是,如果出于某种原因您有 很多 或者您确实需要优化它们的渲染方法(因为您不能为无状态功能组件定义 shouldComponentUpdate)。

      • 如果您知道您的输出依赖于简单的 props/state(“简单”意味着没有嵌套数据结构,因为 PureComponent 执行的是浅层比较)并且您需要/可以获得一些性能改进,请扩展 PureComponent

      • 如果您需要通过在下一个/当前道具和状态之间执行自定义比较逻辑来获得一些性能提升,请扩展 Component 并实现您自己的 shouldComponentUpdate。例如,您可以使用 lodash#isEqual 快速执行深度比较:

        class MyComponent extends Component {
            shouldComponentUpdate (nextProps, nextState) {
                return !_.isEqual(this.props, nextProps) || !_.isEqual(this.state, nextState);
            }
        }
        

      此外,实现您自己的shouldComponentUpdate 或从PureComponent 扩展是优化,并且像往常一样,只有在遇到性能问题时才应该开始研究(avoid premature optimizations)。 根据经验,我总是在应用程序处于工作状态后尝试进行这些优化,并且大多数功能已经实现。当性能问题真正阻碍时,专注于性能问题要容易得多。

      更多详情

      功能性无状态组件:

      这些只是使用函数定义的。由于无状态组件没有内部状态,因此输出(渲染的内容)仅取决于作为该函数输入的道具。

      优点:

      • 在 React 中定义组件的最简单方法。如果您不需要管理任何状态,为什么还要为类和继承而烦恼呢?函数和类之间的主要区别之一是,对于函数,您可以确定输出仅取决于输入(而不取决于先前执行的任何历史记录)。

      • 1234563无需渲染任何东西的逻辑(更容易测试,更可重用等)。

      缺点:

      • 没有生命周期方法。你没有办法定义componentDidMount 和其他朋友。通常,您在层次结构中较高的父组件中执行此操作,这样您就可以将所有子组件变成无状态的组件。

      • 无法手动控制何时需要重新渲染,因为您无法定义 shouldComponentUpdate。每次组件接收到新的道具时都会发生重新渲染(无法进行浅比较等)。未来,React 可以自动优化无状态组件,现在有一些库可以使用。由于无状态组件只是函数,基本上就是“函数记忆”的经典问题。

      • 不支持引用:https://github.com/facebook/react/issues/4936

      扩展 PureComponent 类的组件 VS 扩展 Component 类的普通组件:

      React 曾经有一个 PureRenderMixin,您可以将其附加到使用 React.createClass 语法定义的类。 mixin 将简单地定义一个shouldComponentUpdate,在下一个道具和下一个状态之间进行浅比较,以检查是否有任何变化。如果没有任何变化,则无需执行重新渲染。

      如果你想使用 ES6 语法,你不能使用 mixins。所以为了方便起见,React 引入了一个 PureComponent 类,你可以继承而不是使用 ComponentPureComponent 只是以与PureRendererMixin 相同的方式实现shouldComponentUpdate。这主要是一种方便的事情,因此您不必自己实现它,因为当前/下一个状态和 props 之间的浅比较可能是最常见的场景,可以让您快速获得一些性能提升。

      例子:

      class UserAvatar extends Component {
          render() {
             return <div><img src={this.props.imageUrl} /> {{ this.props.username }} </div>
          }
      } 
      

      如您所见,输出取决于props.imageUrlprops.username。如果在父组件中使用相同的 props 渲染 &lt;UserAvatar username="fabio" imageUrl="http://foo.com/fabio.jpg" /&gt;,React 每次都会调用 render,即使输出完全相同。请记住,尽管 React 实现了 dom diff,因此 DOM 不会实际更新。尽管如此,执行 dom 差异可能会很昂贵,因此在这种情况下,这将是一种浪费。

      如果UserAvatar 组件改为扩展PureComponent,则执行浅比较。而且因为 props 和 nextProps 是一样的,render 根本不会被调用。

      React中“纯”定义的注意事项:

      一般来说,“纯函数”是一个在给定相同输入的情况下总是计算出相同结果的函数。输出(对于 React,这是 render 方法返回的内容)不依赖于任何历史/状态,也没有任何副作用(改变函数外部“世界”的操作)。

      在 React 中,如果你称“无状态”组件为从不调用 this.setState 并且不使用 this.state 的组件,那么根据上面的定义,无状态组件不一定是纯组件。

      事实上,在PureComponent 中,您仍然可以在生命周期方法中执行副作用。例如,您可以在componentDidMount 中发送一个ajax 请求,或者您可以执行一些DOM 计算来动态调整render 中的div 的高度。

      “哑组件”定义具有更“实用”的含义(至少在我的理解中):哑组件“被告知”父组件通过 props 做什么,但不知道该怎么做但使用道具回调代替。

      “智能”AvatarComponent 示例:

      class AvatarComponent extends Component {
          expandAvatar () {
              this.setState({ loading: true });
              sendAjaxRequest(...).then(() => {
                  this.setState({ loading: false });
              });
          }        
      
          render () {
              <div onClick={this.expandAvatar}>
                  <img src={this.props.username} />
              </div>
          }
      }
      

      “哑巴”AvatarComponent 的示例:

      class AvatarComponent extends Component {
          render () {
              <div onClick={this.props.onExpandAvatar}>
                  {this.props.loading && <div className="spinner" />}
                  <img src={this.props.username} />
              </div>
          }
      }
      

      最后我要说的是,“哑”、“无状态”和“纯”是完全不同的概念,有时可以重叠,但不一定,主要取决于您的用例。

      【讨论】:

      • 非常感谢您的回答和您分享的知识。但我真正的问题是我们什么时候应该选择什么?。对于您在回答中提到的相同示例,我应该如何定义它?它应该是功能性无状态组件(如果是,为什么?),还是扩展 PureComponent(为什么?)或扩展 Component 类(又是为什么?)。您如何根据我们组件的用途/大小/道具/行为在这三者之间做出决定,如何选择?
      • 没问题。对于功能性无状态组件,您可以考虑一个优缺点列表来决定这是否合适。这是否回答了你的第一点?我将尝试更多地解决选择问题。
      • 功能组件总是在父组件更新时重新渲染,即使它们根本不使用propsexample.
      • 这是我很长一段时间以来读过的最全面的答案之一。做得好。关于第一句话的评论:当扩展PureComponent时,你不应该实现shouldComponentUpdate()。如果你真的这样做了,你应该会看到一个警告。
      • 为了获得真正的性能提升,您应该尝试将PureComponent 用于具有嵌套对象/数组属性的组件。当然,您必须了解正在发生的事情。如果我理解正确,如果你没有直接改变 props/state(React 试图阻止你使用警告)或通过外部库,那么你应该可以使用 PureComponent 而不是 Component 几乎无处不在...除了非常简单的组件,实际上不使用它可以更快 - 请参阅news.ycombinator.com/item?id=14418576
      【解决方案3】:

      我不是反应过度的天才,但据我了解,我们可以在以下情况下使用每个组件

      1. 无状态组件 -- 这些是没有生命周期的组件,所以这些组件应该用于渲染父组件的重复元素,例如渲染文本列表显示信息并且没有要执行的任何操作。

      2. 纯组件 -- 这些是具有生命周期的项目,当给定一组特定的道具时,它们将始终返回相同的结果。当显示结果列表或没有复杂子元素的特定对象数据并用于执行仅影响自身的操作时,可以使用这些组件。这样的显示用户卡片列表或产品卡片列表(基本产品信息),用户只能执行的操作是点击查看详细信息页面或添加到购物车。

      3. 普通组件或复杂组件 -- 我使用术语复杂组件是因为它们通常是页面级组件并且由许多子组件组成,而且每个子组件都可以以自己独特的方式表现这样你就不能 100% 确定它会在给定状态下呈现相同的结果。正如我所说,通常这些应该用作容器组件

      【讨论】:

      • 这种方法可行,但您可能会错失巨大的性能提升。在根级组件和靠近层次结构顶部的组件中使用PureComponent 通常是您会看到最大性能提升的地方。当然,你确实需要避免直接改变 props 和 state 以使纯组件正常工作,但是直接改变对象在 React 中是一种反模式。
      猜你喜欢
      • 2010-09-05
      • 2021-09-07
      • 2018-10-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-04-17
      相关资源
      最近更新 更多