我们从React v15.3.0中了解到,我们有一个名为PureComponent的新基类,它通过内置的PureRenderMixin进行扩展。我的理解是,在引擎盖下,这采用了对shouldComponentUpdate内部道具的粗略比较
现在我们有3种方法来定义React组件:
- 不扩展任何类的功能性无状态组件
- 扩展
PureComponent类的组件 - 扩展
组件类的普通组件
很久以前,我们曾将无状态组件称为纯组件,甚至是哑组件。似乎是这个词的全部定义;纯粹的;现在,情况发生了变化
虽然我了解这三者之间的基本区别,但我仍然不确定何时选择什么。还有,每种方法的性能影响和权衡是什么
更新:
以下是我希望澄清的问题:
- 我应该选择将我的简单组件定义为功能组件(为了简单)还是扩展
PureComponent类(为了性能) - 性能提升是否是我真正的权衡
我输了 - 当我可以始终使用
PureComponent以获得更好的性能时,我是否需要扩展普通的组件类
您如何决定,如何根据我们组件的用途/大小/道具/行为在这三者之间进行选择
从React.PureComponent或从React.Component使用自定义shouldComponentUpdate方法进行扩展会影响性能。使用无状态功能组件是一种“体系结构”选择,并且没有任何开箱即用的性能优势
-
对于需要轻松重用的简单、仅表示的组件,首选无状态功能组件。通过这种方式,您可以确保它们与实际的应用程序逻辑分离,非常容易测试,并且没有意外的副作用。例外情况是,如果出于某种原因,您有很多这样的组件,或者您确实需要优化它们的渲染方法(因为您不能为无状态功能组件定义
shouldComponentUpdate) -
扩展
PureComponent,如果您知道您的输出依赖于简单的道具/状态(“简单”意味着没有嵌套的数据结构,因为PureComponent执行的是浅层比较),并且您需要/可以获得一些性能改进 -
扩展
组件并实现您自己的shouldComponentUpdate,如果您需要通过在下一个/当前道具和状态之间执行自定义比较逻辑来提高性能。例如,您可以使用lodash#isEqual快速执行深度比较:类MyComponent扩展组件{
shouldComponentUpdate(下一步,下一步状态){
return!u.isEqual(this.props,nextProps)| |!uqual.isEqual(this.state,nextState);
}
}
此外,实现您自己的shouldComponentUpdate或从PureComponent扩展都是优化,通常只有在出现性能问题时才应该开始研究这一点(避免过早优化)。
根据经验,我总是尝试在应用程序处于工作状态后进行这些优化,大多数功能已经实现。当性能问题真正成为阻碍时,更容易关注它们
更多细节
功能性无状态组件:
这些都是使用函数定义的。由于无状态组件没有内部状态,因此输出(呈现的内容)仅取决于作为此函数输入的道具
优点:
-
在React中定义组件的最简单方法。如果您不需要管理任何状态,为什么还要麻烦处理类和继承呢?函数和类之间的主要区别之一是,对于函数,您可以确保输出仅取决于输入(而不取决于以前执行的任何历史记录)
-
理想情况下,在您的应用程序中,您应该尽可能多地使用无状态组件,因为这通常意味着您将逻辑移到了视图层之外,并将其移到了类似redux的位置,这意味着您可以在不必渲染任何内容的情况下测试真正的逻辑(更易于测试,更易于重用,等等)
缺点:
-
没有生命周期方法。您无法定义
componentDidMount和其他朋友。通常在层次结构中较高的父组件中执行此操作,以便可以将所有子组件转换为无状态子组件 -
无法手动控制何时需要重新渲染,因为您无法定义
shouldComponentUpdate。每次组件收到新道具时都会进行重新渲染(无法进行浅层比较等)。将来,React可以自动优化无状态组件,因为现在有一些库可以使用。由于无状态组件只是函数,因此基本上这是“函数记忆”的经典问题 -
不支持引用:https://github.com/facebook/react/issues/4936
扩展PureComponent类的组件与扩展组件类的普通组件相比:
React过去有一个PureRenderMixin可以附加到使用React.createClass语法定义的类。mixin只需定义一个shouldComponentUpdate,在下一个道具和下一个状态之间执行一个粗略的比较,以检查是否有任何变化。如果没有任何更改,则无需执行重新渲染
如果要使用ES6语法,则不能使用mixin。因此,为了方便起见,我们引入了一个PureComponent类,您可以从中继承,而不是使用ComponentPureComponent只是以与pureRenderMixin相同的方式实现shouldComponentUpdate。这基本上是一件方便的事情,因此您不必自己实现它,因为当前/下一个状态和道具之间的肤浅比较可能是最常见的场景,可以让您快速获得性能胜利
例如:
类UserAvatar扩展组件{
render(){
return<;div>;<;img src={this.props.imageUrl}/>;{{this.props.username}}<;/div>;
}
}
如您所见,输出取决于props.imageUrl和props.username。如果在父组件中渲染<;UserAvatar username=“fabio”imageUrl=”http://foo.com/fabio.jpg“/>使用相同的道具,React每次都会调用render,即使输出完全相同。请记住,React实现了dom扩散,因此dom实际上不会被更新。尽管如此,执行dom扩散可能会很昂贵,因此在这种情况下这将是一种浪费
如果UserAvatar组件扩展了PureComponent,则执行浅层比较。由于props和nextrops是相同的,所以根本不会调用render
关于React中“纯”定义的注释:
一般来说,“纯函数”是指在给定相同输入的情况下,其计算结果总是相同的函数。输出(对于React,这是render方法返回的内容)不依赖于任何历史/状态,也没有任何副作用(改变函数外部“世界”的操作)
在React中,如果您将“无状态”组件称为从不调用this.setState且不使用this.state的组件,则根据上述定义,无状态组件不一定是纯组件
事实上,在PureComponent中,您仍然可以在生命周期方法期间执行副作用。例如,您可以在componentDidMount中发送一个ajax请求,或者执行一些DOM计算来动态调整render中div的高度
“Dumb components”的定义有一个更“实际”的含义(至少在我的理解中):一个Dumb component通过props被父组件“告知”要做什么,并且不知道如何做,而是使用props回调
“智能”化身组件的示例:
类虚拟组件扩展组件{
扩展化身(){
this.setState({loading:true});
sendAjaxRequest(…)。然后(()=>{
this.setState({loading:false});
});
}
渲染(){
<;div onClick={this.expandAvatar}>;
<;img src={this.props.username}/>;
<;/div>;
}
}
“哑”化身组件的示例:
类虚拟组件扩展组件{
渲染(){
<;div onClick={this.props.onExpandAvatar}>;
{this.props.load&;<;div className=“spinner”/>;}
<;img src={this.props.username}/>;
<;/div>;
}
}
最后,我要说的是,“哑”、“无状态”和“纯”是完全不同的概念,有时可以重叠,但不一定重叠,这主要取决于您的用例