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

2022-08-30 01:23:53

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

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

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

不久前,我们曾经将无状态组件称为纯组件,甚至是哑组件。似乎“纯”这个词的整个定义现在在 React 中都发生了变化。

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


更新

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

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

答案 1

您如何根据组件的目的/大小/道具/行为来决定,如何在这三者之间进行选择?

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

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

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

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

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

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

更多详情

功能性无状态组件:

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

优点:

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

  • 理想情况下,在你的应用中,你应该以拥有尽可能多的无状态组件为目标,因为这通常意味着你将逻辑移动到视图层之外,并将其移动到类似redux的东西,这意味着你可以测试你的真实逻辑,而不必渲染任何东西(更容易测试,更可重用等)。

缺点:

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

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

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

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

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

如果要使用 ES6 语法,则不能使用 mixins。因此,为了方便起见,React 引入了一个你可以从中继承的类,而不是使用 . 只是以与 .这主要是一个方便的事情,所以你不必自己实现它,因为当前/下一个状态和道具之间的浅层比较可能是最常见的场景,可以给你一些快速的性能胜利。PureComponentComponentPureComponentshouldComponentUpdatePureRendererMixin

例:

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

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

如果组件扩展,则执行浅比较。而且因为道具和下一个道具是一样的,根本不会被调用。UserAvatarPureComponentrender

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

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

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

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

“哑组件”的定义具有更“实用”的含义(至少在我的理解中):一个哑组件通过 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>
    }
}

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


答案 2

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

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

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

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