反应功能性无状态组件,PureComponent,Component;有什么区别,什么时候应该使用什么?


188

才知道,从阵营v15.3.0,我们有一个新的基类叫PureComponent与扩展PureRenderMixin内置。我了解的是,在幕后,它对内部的道具进行了浅浅的比较shouldComponentUpdate

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

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

一段时间以前,我们曾经将无状态组件称为“纯组件”,甚至称为“哑组件”。似乎“纯”一词的整个定义现在已经在React中改变了。

尽管我了解这三者之间的基本区别,但是我仍然不确定何时选择什么。还有每种性能影响和权衡取舍是什么?


更新

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

  • 我应该选择将我的简单组件定义为功能性的(出于简单性考虑)还是扩展PureComponent类(出于性能的考虑)?
  • 我所获得的性能提升是否是我失去的简单性的真正折衷呢?
  • Component当我总是可以使用PureComponent以获得更好的性能时,是否需要扩展常规类?

Answers:


315

您如何决定,如何根据我们组件的目的/尺寸/道具/行为在这三个之间进行选择?

从自定义方法扩展React.PureComponent或从React.Component自定义shouldComponentUpdate方法扩展具有性能影响。使用无状态功能组件是一种“架构”选择,并且没有任何现成的性能优势。

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

  • 扩展PureComponent如果你知道你的输出依赖于简单的道具/状态(“简单”意味着没有嵌套的数据结构,作为PureComponent执行浅比较)以及你需要/可以得到一些性能改进。

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

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

另外,实施您自己的优化shouldComponentUpdate或从中PureComponent进行优化,通常,只有在遇到性能问题时才应开始研究(避免过早的优化)。根据经验,我总是尝试在应用程序处于工作状态并且已经实现了大多数功能之后进行这些优化。当实际遇到问题时,关注它们会容易得多。

更多细节

功能性无状态组件:

这些仅使用函数定义。由于无状态组件没有内部状态,因此输出(呈现的内容)仅取决于作为此函数的输入给出的props。

优点:

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

  • 理想情况下,在您的应用中,您应该致力于拥有尽可能多的无状态组件,因为这通常意味着您将逻辑移到了视图层之外,然后将其移至了redux之类,这意味着您可以测试真实的逻辑而无需渲染任何东西(更容易测试,更可重用等)。

缺点:

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

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

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

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

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

如果要使用ES6语法,则不能使用mixins。为了方便起见,React引入了一个PureComponent您可以继承而不使用的类ComponentPureComponent只是shouldComponentUpdate以与相同的方式实现PureRendererMixin。这主要是方便的事情,因此您不必自己实现,因为当前/下一个状态与道具之间的浅层比较可能是最常见的情况,可以使您快速获得性能。

例:

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

如您所见,输出取决于props.imageUrlprops.username。如果在父组件中<UserAvatar username="fabio" imageUrl="http://foo.com/fabio.jpg" />使用相同的道具进行渲染,则render即使输出完全相同,React也会每次调用。请记住,尽管React实现了dom diff,所以DOM实际上不会被更新。尽管如此,执行dom diff仍然很昂贵,因此在这种情况下将是浪费。

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

关于React中“纯”的定义的注释:

通常,“纯函数”是在给定相同输入的情况下始终对相同结果求值的函数。输出(对于React,这就是方法返回的结果render)不依赖于任何历史记录/状态,也没有任何副作用(在函数外部更改“世界”的操作)。

在React中,如果您将“无状态”组件称为永不调用this.setState且不使用的组件,则根据上述定义,无状态组件不一定是纯组件this.state

实际上,在中PureComponent,您仍然可以在生命周期方法中执行副作用。例如,您可以在内部发送ajax请求componentDidMount,也可以执行DOM计算以动态调整内div的高度render

“愚蠢的组件”定义具有更“实用”的含义(至少在我的理解中):一个愚蠢的组件“通过道具被告知”父组件要做什么,并且不知道该怎么做,而是使用道具回调。

“智能”示例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>
    }
}

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


1
非常感谢您的回答以及您分享的知识。但是我真正的问题是,我们什么时候应该选择什么?。对于您在答案中提到的相同示例,我应该如何定义它?它应该是功能性的无状态组件(如果是,为什么呢?),还是扩展PureComponent(为什么?)或扩展Component类(还是为什么?)?您如何决定,如何根据我们组件的目的/尺寸/道具/行为在这三个之间进行选择?
Yadhu Kiran

1
没问题。对于功能性无状态组件,有一个优点/缺点列表,您可以考虑决定是否合适。这是否能回答您的第一点?我将尝试进一步解决选择问题。
fabio.sussetto

2
即使父组件完全不使用props,功能组件也总是在父组件更新时重新渲染。例子
AlexM

1
这是我已经阅读了很长时间的最全面的答案之一。做得好。关于第一句话的一个评论:扩展时PureComponent,您不应实现shouldComponentUpdate()。如果您确实执行此操作,则应该看到警告。
jjramos

1
为了获得真正的性能,您应该尝试将PureComponent具有嵌套对象/数组属性的组件用于组件。当然,您必须知道发生了什么。如果我理解正确,如果您不是直接更改props / state(React尝试通过警告来阻止您这样做),也不是通过外部库进行更改,那么您应该可以使用PureComponent而不是在Component很多地方使用...非常简单的组件,实际上可以更快地不使用它-请参阅news.ycombinator.com/item?id=14418576
Matt Browne

28

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

  1. 无状态组件- 这些组件没有生命周期,因此应在呈现父组件的重复元素时使用这些组件,例如呈现仅显示信息且没有任何动作要执行的文本列表。

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

  3. 普通组件或复杂组件-我使用了术语“复杂组件”,因为它们通常是页面级组件,由很多子组件组成,并且由于每个子组件都可以以自己独特的方式运行,因此您不能百分百确定它会在给定状态下呈现相同的结果。正如我通常所说的,这些应该用作容器组件


1
这种方法可能有效,但是您可能会错过大量的性能提升。PureComponent在根级别的组件和层次结构顶部附近的组件中使用通常会带来最大的性能提升。当然,您确实需要避免直接更改道具和状态,以使纯组件正常工作,但是无论如何,直接更改对象是React中的反模式。
马特·布朗

5
  • React.Component是默认的“常规”组件。您可以使用class关键字和声明它们extends React.Component。将它们视为具有生命周期方法,事件处理程序以及任何方法的类。

  • React.PureComponentReact.Component实现shouldComponentUpdate()与做它的一个比较浅的功能propsstate。你必须使用forceUpdate(),如果你知道组件有道具或状态嵌套数据更改,您希望重新呈现。因此,如果当您作为道具传递或设置为状态的数组或对象发生变化时需要重新渲染组件,则它们并不是很好。

  • 功能组件是没有生命周期功能的组件。它们据说是无状态的,但是它们是如此干净整洁,以至于我们现在有了钩子(自React 16.8起),因此您仍然可以拥有状态。所以我想它们只是“干净的组件”。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.