We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
PureComponent 最早在 React v15.3 版本中发布,主要是为了优化 React 应用而产生。
PureComponent
class Counter extends React.PureComponent { constructor(props) { super(props); this.state = {count: 1}; } render() { return ( <button color={this.props.color} onClick={() => this.setState(state => ({count: state.count + 1}))}> Count: {this.state.count} </button> ); } }
在这段代码中, React.PureComponent 会浅比较 props.color 或 state.count 是否改变,来决定是否重新渲染组件。
React.PureComponent
props.color
state.count
实现
React.PureComponent 和 React.Component 类似,都是定义一个组件类。不同是 React.Component 没有实现 shouldComponentUpdate(),而 React.PureComponent 通过 props 和 state 的 浅比较 实现了。
React.Component
shouldComponentUpdate()
使用场景
当 React.Component 的 props 和 state 均为基本类型,使用 React.PureComponent 会节省应用的性能
可能出现的问题及解决方案
当props 或 state 为 复杂的数据结构 (例如:嵌套对象和数组)时,因为 React.PureComponent 仅仅是 浅比较 ,可能会渲染出 错误的结果 。这时有 两种解决方案 :
注意
React.PureComponent 中的 shouldComponentUpdate() 将跳过所有子组件树的 prop 更新(具体原因参考 Hooks 与 React 生命周期:即:更新阶段,由父至子去判断是否需要重新渲染),所以使用 React.PureComponent 的组件,它的所有 子组件也必须都为 React.PureComponent 。
如果你在 render 方法里创建函数,那么使用 props 会抵消使用 React.PureComponent 带来的优势。因为每次渲染运行时,都会分配一个新函数,如果你有子组件,即使数据没有改变,它们也会重新渲染,因为浅比较 props 的时候总会得到 false。
render
props
false
例如:
// FriendsItem 在父组件引用样式 <FriendsItem key={friend.id} name={friend.name} id={friend.id} onDeleteClick={() => this.deleteFriends(friend.id)} /> // 在父组件中绑定 // 父组件在 props 中传递了一个箭头函数。箭头函数在每次 render 时都会重新分配(和使用 bind 的方式相同)
其中,FriendsItem 为 PureComponent:
FriendsItem
// 其中 FriendsItem 为 PureComponent class FriendsItem extends React.PureComponent { render() { const { name, onDeleteClick } = this.props console.log(`FriendsItem:${name} 渲染`) return ( <div> <span>{name}</span> <button onClick={onDeleteClick}>删除</button> </div> ) } } // 每次点击删除操作时,未删除的 FriendsItem 都将被重新渲染
点击查看在线实例
这种在 FriendsItem 直接调用 () => this.deleteFriends(friend.id),看起来操作更简单,逻辑更清晰,但它有一个有一个最大的弊端,甚至打破了像 shouldComponentUpdate 和 PureComponent 这样的性能优化。
() => this.deleteFriends(friend.id)
shouldComponentUpdate
这是因为:父组件在 render 声明了一个函数onDeleteClick,每次父组件渲染都会重新生成新的函数。因此,每次父组件重新渲染,都会给每个子组件 FriendsItem 传递不同的 props,导致每个子组件都会重新渲染, 即使 FriendsItem 为 PureComponent。
onDeleteClick
避免在 render 方法里创建函数并使用它。它会打破了像 shouldComponentUpdate 和 PureComponent 这样的性能优化。
要解决这个问题,只需要将原本在父组件上的绑定放到子组件上即可。FriendsItem 将始终具有相同的 props,并且永远不会导致不必要的重新渲染。
// FriendsItem 在父组件引用样式 <FriendsItem key={friend.id} id={friend.id} name={friend.name} onClick={this.deleteFriends} />
FriendsItem:
class FriendsItem extends React.PureComponent { onDeleteClick = () => { this.props.onClick(this.props.id) } // 在子组件中绑定 render() { const { name } = this.props console.log(`FriendsItem:${name} 渲染`) return ( <div> <span>{name}</span> <button onClick={this.onDeleteClick}>删除</button> </div> ) } } // 每次点击删除操作时,FriendsItem 都不会被重新渲染
通过此更改,当单击删除操作时,其他 FriendsItem 都不会被重新渲染了 👍
考虑一个文章列表,您的个人资料组件将从中显示用户最喜欢的 10 个作品。
render() { const { posts } = this.props // 在渲染函数中生成 topTen,并渲染 const topTen = [...posts].sort((a, b) => b.likes - a.likes).slice(0, 9) return //... } // 这会导致组件每次重新渲染,都会生成新的 topTen,导致不必要的渲染
topTen每次组件重新渲染时都会有一个全新的引用,即使 posts 没有更改,派生 state 也是相同的。
topTen
posts
state
这个时候,我们应该将 topTen 的判断逻辑提取到 render 函数之外,通过缓存派生 state 来解决此问题。
例如,在组件的状态中设置派生 state,并仅在 posts 已更新时更新。
componentWillMount() { this.setTopTenPosts(this.props.posts) } componentWillReceiveProps(nextProps) { if (this.props.posts !== nextProps.posts) { this.setTopTenPosts(nextProps.posts) } } // 每次 posts 更新时,更新派生 state,而不是在渲染函数中重新生成 setTopTenPosts(posts) { this.setState({ topTen: [...posts].sort((a, b) => b.likes - a.likes).slice(0, 9) }) }
在使用 PureComponent 时,请注意:
对于 React 开发人员来说,知道何时在代码中使用 Component,**PureComponent ** 和 Stateless Functional Component 非常重要。
首先,让我们看一下无状态组件。
输入输出数据完全由 props 决定,而且不会产生任何副作用。
const Button = props => <button onClick={props.onClick}> {props.text} </button>
无状态组件可以通过减少继承 Component 而来的生命周期函数而达到性能优化的效果。从本质上来说,无状态组件就是一个单纯的 render 函数,所以无状态组件的缺点也是显而易见的。因为它没有 shouldComponentUpdate 生命周期函数,所以每次 state 更新,它都会重新绘制 render 函数。
Component
React 16.8 之后,React 引入 Hooks 。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。
PureComponent 提高了性能,因为它减少了应用程序中的渲染操作次数,这对于复杂的 UI 来说是一个巨大的胜利,因此建议尽可能使用。此外,还有一些情况需要使用 Component 的生命周期方法,在这种情况下,我们不能使用无状态组件。
无状态组件易于实施且快速实施。它们适用于非常小的 UI 视图,其中重新渲染成本无关紧要。它们提供更清晰的代码和更少的文件来处理。
React.memo 为高阶组件。它实现的效果与 React.PureComponent 相似,不同的是:
React.memo
function MyComponent(props) { /* 使用 props 渲染 */ } // 比较函数 function areEqual(prevProps, nextProps) { /* 如果把 nextProps 传入 render 方法的返回结果与 将 prevProps 传入 render 方法的返回结果一致则返回 true, 否则返回 false 返回 true,复用最近一次渲染 返回 false,重新渲染 */ } export default React.memo(MyComponent, areEqual);
areEqual
true
// 新建了空方法ComponentDummy ,ComponentDummy 的原型 指向 Component 的原型; function ComponentDummy() {} ComponentDummy.prototype = Component.prototype; /** * Convenience component with default shallow equality check for sCU. */ function PureComponent(props, context, updater) { this.props = props; this.context = context; // If a component has string refs, we will assign a different object later. this.refs = emptyObject; this.updater = updater || ReactNoopUpdateQueue; } // 解析同 React.Component,详细请看上一章 /** * 实现 React.PureComponent 对 React.Component 的原型继承 */ /** * 用 ComponentDummy 的原因是为了不直接实例化一个 Component 实例,可以减少一些内存使用 * * 因为,我们这里只需要继承 React.Component 的 原型,直接 PureComponent.prototype = new Component() 的话 * 会继承包括 constructor 在内的其他 Component 属性方法,但是 PureComponent 已经有自己的 constructor 了, * 再继承的话,造成不必要的内存消耗 * 所以会新建ComponentDummy,只继承Component的原型,不包括constructor,以此来节省内存。 */ const pureComponentPrototype = (PureComponent.prototype = new ComponentDummy()); // 修复 pureComponentPrototype 构造函数指向 pureComponentPrototype.constructor = PureComponent; // Avoid an extra prototype jump for these methods. // 虽然上面两句已经让PureComponent继承了Component // 但多加一个 Object.assign(),能有效的避免多一次原型链查找 Object.assign(pureComponentPrototype, Component.prototype); // 唯一的区别,原型上添加了 isPureReactComponent 属性去表示该 Component 是 PureComponent // 在后续组件渲染的时候,react-dom 会去判断 isPureReactComponent 这个属性,来确定是否浅比较 props、status 实现更新 /** 在 ReactFiberClassComponent.js 中,有对 isPureReactComponent 的判断 if (ctor.prototype && ctor.prototype.isPureReactComponent) { return ( !shallowEqual(oldProps, newProps) || !shallowEqual(oldState, newState) ); } */ pureComponentPrototype.isPureReactComponent = true;
这里只是 PureComponent 的声明创建,至于如何实现 shouldComponentUpdate() ,核心代码在:
// ReactFiberClassComponent.js function checkShouldComponentUpdate( workInProgress, ctor, oldProps, newProps, oldState, newState, nextContext, ) { // ... if (ctor.prototype && ctor.prototype.isPureReactComponent) { // 如果是纯组件,比较新老 props、state // 返回 true,重新渲染, // 即 shallowEqual props 返回 false,或 shallowEqual state 返回 false return ( !shallowEqual(oldProps, newProps) || !shallowEqual(oldState, newState) ); } return true; }
shallowEqual.js
/** * 通过遍历对象上的键并返回 false 来执行相等性 * 在参数列表中,当任意键对应的值不严格相等时,返回 false。 * 当所有键的值严格相等时,返回 true。 */ function shallowEqual(objA: mixed, objB: mixed): boolean { // 通过 Object.is 判断 objA、objB 是否相等 if (is(objA, objB)) { return true; } if ( typeof objA !== 'object' || objA === null || typeof objB !== 'object' || objB === null ) { return false; } // 参数列表 const keysA = Object.keys(objA); const keysB = Object.keys(objB); // 参数列表长度不相同 if (keysA.length !== keysB.length) { return false; } // 比较参数列表每一个参数,但仅比较一层 for (let i = 0; i < keysA.length; i++) { if ( !hasOwnProperty.call(objB, keysA[i]) || !is(objA[keysA[i]], objB[keysA[i]]) ) { return false; } } return true; }
Object.is() 判断两个值是否相同。
Object.is()
这种相等性判断逻辑和传统的 == 运算不同,== 运算符会对它两边的操作数做隐式类型转换(如果它们类型不同),然后才进行相等性比较,(所以才会有类似 "" == false 等于 true 的现象),但 Object.is 不会做这种类型转换。
==
"" == false
Object.is
这与 === 运算符的判定方式也不一样。=== 运算符(和== 运算符)将数字值 -0 和 +0 视为相等,并认为 Number.NaN 不等于 NaN。
===
-0
+0
Number.NaN
NaN
如果下列任何一项成立,则两个值相同:
undefined
null
The text was updated successfully, but these errors were encountered:
No branches or pull requests
一、PureComponent
PureComponent
最早在 React v15.3 版本中发布,主要是为了优化 React 应用而产生。在这段代码中,
React.PureComponent
会浅比较props.color
或state.count
是否改变,来决定是否重新渲染组件。实现
React.PureComponent
和React.Component
类似,都是定义一个组件类。不同是React.Component
没有实现shouldComponentUpdate()
,而React.PureComponent
通过 props 和 state 的 浅比较 实现了。使用场景
当
React.Component
的 props 和 state 均为基本类型,使用React.PureComponent
会节省应用的性能可能出现的问题及解决方案
当props 或 state 为 复杂的数据结构 (例如:嵌套对象和数组)时,因为
React.PureComponent
仅仅是 浅比较 ,可能会渲染出 错误的结果 。这时有 两种解决方案 :注意
React.PureComponent
中的shouldComponentUpdate()
将跳过所有子组件树的 prop 更新(具体原因参考 Hooks 与 React 生命周期:即:更新阶段,由父至子去判断是否需要重新渲染),所以使用 React.PureComponent 的组件,它的所有 子组件也必须都为 React.PureComponent 。二、使用 PureComponent 常见误区
误区一:在渲染方法中创建函数
如果你在
render
方法里创建函数,那么使用props
会抵消使用React.PureComponent
带来的优势。因为每次渲染运行时,都会分配一个新函数,如果你有子组件,即使数据没有改变,它们也会重新渲染,因为浅比较props
的时候总会得到false
。例如:
其中,
FriendsItem
为PureComponent
:点击查看在线实例
这种在
FriendsItem
直接调用() => this.deleteFriends(friend.id)
,看起来操作更简单,逻辑更清晰,但它有一个有一个最大的弊端,甚至打破了像shouldComponentUpdate
和PureComponent
这样的性能优化。这是因为:父组件在
render
声明了一个函数onDeleteClick
,每次父组件渲染都会重新生成新的函数。因此,每次父组件重新渲染,都会给每个子组件FriendsItem
传递不同的props
,导致每个子组件都会重新渲染, 即使FriendsItem
为PureComponent
。避免在 render 方法里创建函数并使用它。它会打破了像 shouldComponentUpdate 和 PureComponent 这样的性能优化。
要解决这个问题,只需要将原本在父组件上的绑定放到子组件上即可。
FriendsItem
将始终具有相同的props
,并且永远不会导致不必要的重新渲染。FriendsItem
:点击查看在线实例
通过此更改,当单击删除操作时,其他
FriendsItem
都不会被重新渲染了 👍误区二:在渲染方法中派生 state
考虑一个文章列表,您的个人资料组件将从中显示用户最喜欢的 10 个作品。
topTen
每次组件重新渲染时都会有一个全新的引用,即使posts
没有更改,派生state
也是相同的。这个时候,我们应该将
topTen
的判断逻辑提取到render
函数之外,通过缓存派生state
来解决此问题。例如,在组件的状态中设置派生
state
,并仅在posts
已更新时更新。总结
在使用
PureComponent
时,请注意:PureComponent
时,问题会更加复杂。三、PureComponent 与 Stateless Functional Component
对于 React 开发人员来说,知道何时在代码中使用 Component,**PureComponent ** 和 Stateless Functional Component 非常重要。
首先,让我们看一下无状态组件。
无状态组件
输入输出数据完全由
props
决定,而且不会产生任何副作用。无状态组件可以通过减少继承
Component
而来的生命周期函数而达到性能优化的效果。从本质上来说,无状态组件就是一个单纯的render
函数,所以无状态组件的缺点也是显而易见的。因为它没有shouldComponentUpdate
生命周期函数,所以每次state
更新,它都会重新绘制render
函数。React 16.8 之后,React 引入 Hooks 。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。
何时使用
PureComponent
?PureComponent
提高了性能,因为它减少了应用程序中的渲染操作次数,这对于复杂的 UI 来说是一个巨大的胜利,因此建议尽可能使用。此外,还有一些情况需要使用Component
的生命周期方法,在这种情况下,我们不能使用无状态组件。何时使用无状态组件?
无状态组件易于实施且快速实施。它们适用于非常小的 UI 视图,其中重新渲染成本无关紧要。它们提供更清晰的代码和更少的文件来处理。
四、PureComponent 与 React.memo
React.memo
为高阶组件。它实现的效果与React.PureComponent
相似,不同的是:React.memo
用于函数组件React.PureComponent
适用于 class 组件React.PureComponent
只是浅比较props
、state
,React.memo
也是浅比较,但它可以自定义比较函数React.memo
React.memo
通过记忆组件渲染结果的方式实现 ,提高组件的性能props
浅比较,如果相同,React 将跳过渲染组件的操作并直接复用最近一次渲染的结果。shouldComponentUpdate()
方法不同的是,如果 props 相等,areEqual
会返回true
;如果 props 不相等,则返回false
。这与shouldComponentUpdate
方法的返回值相反。五、PureComponent 源码解析
这里只是
PureComponent
的声明创建,至于如何实现shouldComponentUpdate()
,核心代码在:shallowEqual.js
附:Object.is(来自MDN)
Object.is()
判断两个值是否相同。这种相等性判断逻辑和传统的
==
运算不同,==
运算符会对它两边的操作数做隐式类型转换(如果它们类型不同),然后才进行相等性比较,(所以才会有类似"" == false
等于true
的现象),但Object.is
不会做这种类型转换。这与
===
运算符的判定方式也不一样。===
运算符(和==
运算符)将数字值-0
和+0
视为相等,并认为Number.NaN
不等于NaN
。如果下列任何一项成立,则两个值相同:
undefined
null
true
或者都是false
+0
-0
NaN
NaN
外的其它同一个数字The text was updated successfully, but these errors were encountered: