您如何决定,如何根据我们组件的用途/大小/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.imageUrl
和props.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>
}
}
最后,我会说“愚蠢”、“无状态”和“纯”是完全不同的概念,有时可能会重叠,但不一定,主要取决于您的用例。