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

Yad*_*ran 181 javascript ecmascript-6 reactjs

才知道,从阵营v15.3.0,我们有一个新的名为基类PureComponent与扩展PureRenderMixin内置.我所理解的是,在引擎盖下,它采用了内部道具的浅层比较shouldComponentUpdate.

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

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

一段时间后,我们习惯将无状态组件称为纯组件,甚至是哑组件.似乎"纯"这个词的整个定义现在在React中已经改变了.

虽然我理解这三者之间的基本差异,但我仍然不确定何时选择什么.还有什么是性能影响和权衡取舍?


更新:

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

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

fab*_*tto 304

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

使用自定义方法扩展React.PureComponent或从中扩展具有性能影响.使用无状态功能组件是一种"架构"选择,并且没有任何开箱即用的性能优势(尚未).React.ComponentshouldComponentUpdate

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

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

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

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

此外,实现自己的shouldComponentUpdate或扩展PureComponent是优化,并且像往常一样,只有在遇到性能问题时才应该开始研究(避免过早优化).根据经验,我总是尝试在应用程序处于工作状态后进行这些优化,并且已经实现了大多数功能.当实际出现问题时,关注性能问题要容易得多.

更多细节

功能无状态组件:

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

优点:

  • 在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可以继承而不是使用的类Component.PureComponent只是shouldComponentUpdate以相同的方式实现PureRendererMixin.它主要是一个方便的东西,所以你不必自己实现它,因为当前/下一个状态和道具之间的浅层比较可能是最常见的场景,可以给你一些快速的性能胜利.

例:

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

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

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

关于React中"纯"的定义的注释:

通常,"纯函数"是在给定相同输入的情况下始终评估相同结果的函数.输出(对于React,即render方法返回的内容)不依赖于任何历史/状态,并且它没有任何副作用(操作改变了函数之外的"世界").

在React中,如果将"无状态"称为从不调用this.setState且不使用的组件,则无状态组件不一定是上面定义的纯组件this.state.

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

"Dumb组件"定义具有更"实用"的含义(至少在我的理解中):一个愚蠢的组件"被告知"父组件通过道具做什么,并且不知道如何做但是使用道具而是回调.

"聪明"的例子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>
    }
}
Run Code Online (Sandbox Code Playgroud)

"哑"的例子AvatarComponent:

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

最后我会说"哑巴","无国籍"和"纯粹"是完全不同的概念,有时可以重叠,但不一定,主要取决于你的用例.

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

abh*_*006 24

我不是一个天才的反应,但从我的理解,我们可以在以下情况下使用每个组件

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

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

  3. 正常组件或复杂组件 -我使用了术语复杂组件,因为它们通常是页面级组件,并且包含许多子组件,并且因为每个子组件都可以以其自己独特的方式运行,因此您无法100%确定它将在给定状态下呈现相同的结果.正如我所说,这些应该用作容器组件

  • 这种方法可能有效,但您可能会错过巨大的性能提升。在根级组件和靠近层次结构顶部的组件中使用“PureComponent”通常是您会看到最大性能提升的地方。当然,为了让纯组件正常工作,你确实需要避免直接改变 props 和 state,但是直接改变对象在 React 中是一种反模式。 (2认同)

bel*_*nta 5

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

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

  • 功能组件是没有生命周期功能的组件。它们被认为是无状态的,但它们非常漂亮和干净,以至于我们现在有了钩子(自 React 16.8 起),因此您仍然可以拥有状态。所以我猜它们只是“干净的组件”。