React 函数式无状态组件、PureComponent、Component;有什么区别,我们什么时候应该使用什么?

IT技术 javascript reactjs ecmascript-6
2021-01-29 16:41:36

才知道,从阵营v15.3.0,我们有一个新的基类叫PureComponent与扩展PureRenderMixin内置。我的理解是,在引擎盖下,这使用了对 props 内部的浅层比较shouldComponentUpdate

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

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

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

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


更新

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

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

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

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

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

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

  • 如果您需要通过在 next/current props 和 state 之间执行自定义比较逻辑来获得一些性能提升,则扩展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. 每次组件收到新的 props 时都会重新渲染(无法进行浅层比较等)。将来,React 可以自动优化无状态组件,现在有一些库可以使用。由于无状态组件只是函数,基本上就是“函数记忆”的经典问题。

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

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

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

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

例子:

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" />使用相同的 props渲染,Reactrender每次都会调用,即使输出完全相同。请记住,尽管 React 实现了 dom diffing,因此 DOM 不会实际更新。尽管如此,执行 dom diffing 可能很昂贵,所以在这种情况下,这将是一种浪费。

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

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

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

在 React 中,如果您将“无状态”称为从不调用this.setState且不使用this.state.

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

“哑组件”定义有一个更“实用”的含义(至少在我的理解中):一个哑组件通过 props “被告知”父组件要做什么,并且不知道如何做事但使用 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,你不应该实现shouldComponentUpdate()如果你真的这样做了,你应该看到一个警告。
2021-03-19 16:41:36
为了获得真正的性能提升,您应该尝试使用PureComponent具有嵌套对象/数组属性的组件。当然,您必须了解正在发生的事情。如果我理解正确,如果您没有直接改变 props/state(React 试图阻止您使用警告进行更改)或通过外部库,那么您应该可以使用PureComponent而不是Component几乎无处不在……但有例外非常简单的组件,实际上可以更快地不使用它 - 请参阅news.ycombinator.com/item?id=14418576
2021-03-23 16:41:36
没问题。对于功能性无状态组件,有一个优点/缺点列表,您可以考虑决定它是否适合。这是否回答了你的第一点?我将尝试更多地解决选择问题。
2021-04-02 16:41:36
功能组件总是在父组件更新时重新渲染,即使它们根本不使用props例子
2021-04-06 16:41:36
我非常感谢您的回答和您分享的知识。但我真正的问题是我们什么时候应该选择什么?. 对于您在回答中提到的相同示例,我应该如何定义它?它应该是功能性无状态组件(如果是,为什么?),还是扩展 PureComponent(为什么?)或扩展 Component 类(又是为什么?)。您如何决定,如何根据我们组件用途/大小/道具/行为在这三个之间进行选择
2021-04-11 16:41:36

我不是react的天才,但根据我的理解,我们可以在以下情况下使用每个组件

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

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

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

这种方法可能有效,但您可能会错过巨大的性能提升。PureComponent在根级组件和层次结构顶部附近的组件中使用通常是您看到最大性能提升的地方。当然,为了让纯组件正常工作,您确实需要避免直接改变 props 和 state,但无论如何,直接改变对象在 React 中是一种反模式。
2021-03-26 16:41:36
  • React.Component是默认的“正常”组件。您可以使用class关键字 and声明它们extends React.Component将它们视为一个类,具有生命周期方法、事件处理程序和任何方法。

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

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