PureComponent
最先在 React v15.3 版本中发布,主要是为了优化 React 应用而产生。前端
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
React.PureComponent
和 React.Component
相似,都是定义一个组件类。不一样是 React.Component
没有实现 shouldComponentUpdate()
,而 React.PureComponent
经过 props 和 state 的 浅比较 实现了。git
当 React.Component
的 props 和 state 均为基本类型,使用 React.PureComponent
会节省应用的性能github
可能出现的问题及解决方案数组
当props 或 state 为 复杂的数据结构 (例如:嵌套对象和数组)时,由于 React.PureComponent
仅仅是 浅比较 ,可能会渲染出 错误的结果 。这时有 两种解决方案 :缓存
React.PureComponent
中的 shouldComponentUpdate()
将跳过全部子组件树的 prop 更新(具体缘由参考 Hooks 与 React 生命周期:即:更新阶段,由父至子去判断是否须要从新渲染),因此使用 React.PureComponent 的组件,它的全部 子组件也必须都为 React.PureComponent 。性能优化
对于 React 开发人员来讲,知道什么时候在代码中使用 Component,PureComponent 和 Stateless Functional Component 很是重要。数据结构
首先,让咱们看一下无状态组件。less
输入输出数据彻底由 props
决定,并且不会产生任何反作用。dom
const Button = props => <button onClick={props.onClick}> {props.text} </button>
无状态组件能够经过减小继承 Component
而来的生命周期函数而达到性能优化的效果。从本质上来讲,无状态组件就是一个单纯的 render
函数,因此无状态组件的缺点也是显而易见的。由于它没有 shouldComponentUpdate
生命周期函数,因此每次 state
更新,它都会从新绘制 render
函数。
React 16.8 以后,React 引入 Hooks 。它可让你在不编写 class 的状况下使用 state 以及其余的 React 特性。
PureComponent
?PureComponent
提升了性能,由于它减小了应用程序中的渲染操做次数,这对于复杂的 UI 来讲是一个巨大的胜利,所以建议尽量使用。此外,还有一些状况须要使用 Component
的生命周期方法,在这种状况下,咱们不能使用无状态组件。
无状态组件易于实施且快速实施。它们适用于很是小的 UI 视图,其中从新渲染成本可有可无。它们提供更清晰的代码和更少的文件来处理。
React.memo
为高阶组件。它实现的效果与 React.PureComponent
类似,不一样的是:
React.memo
用于函数组件React.PureComponent
适用于 class 组件React.PureComponent
只是浅比较 props
、state
,React.memo
也是浅比较,但它能够自定义比较函数function MyComponent(props) { /* 使用 props 渲染 */ } // 比较函数 function areEqual(prevProps, nextProps) { /* 若是把 nextProps 传入 render 方法的返回结果与 将 prevProps 传入 render 方法的返回结果一致则返回 true, 不然返回 false 返回 true,复用最近一次渲染 返回 false,从新渲染 */ } export default React.memo(MyComponent, areEqual);
React.memo
经过记忆组件渲染结果的方式实现 ,提升组件的性能props
浅比较,若是相同,React 将跳过渲染组件的操做并直接复用最近一次渲染的结果。shouldComponentUpdate()
方法不一样的是,若是 props 相等,areEqual
会返回 true
;若是 props 不相等,则返回 false
。这与 shouldComponentUpdate
方法的返回值相反。若是你在 render
方法里建立函数,那么使用 props
会抵消使用 React.PureComponent
带来的优点。由于每次渲染运行时,都会分配一个新函数,若是你有子组件,即便数据没有改变,它们也会从新渲染,由于浅比较 props
的时候总会获得 false
。
例如:
// FriendsItem 在父组件引用样式 <FriendsItem key={friend.id} name={friend.name} id={friend.id} onDeleteClick={() => this.deleteFriends(friend.id)} /> // 在父组件中绑定 // 父组件在 props 中传递了一个箭头函数。箭头函数在每次 render 时都会从新分配(和使用 bind 的方式相同)
其中,FriendsItem
为 PureComponent
:
// 其中 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
这样的性能优化。
这是由于:父组件在 render
声明了一个函数onDeleteClick
,每次父组件渲染都会从新生成新的函数。所以,每次父组件从新渲染,都会给每一个子组件 FriendsItem
传递不一样的 props
,致使每一个子组件都会从新渲染, 即便 FriendsItem
为 PureComponent
。
避免在 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
的判断逻辑提取到 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
时,请注意:
PureComponent
时,问题会更加复杂。// 新建了空方法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()
判断两个值是否相同。
这种相等性判断逻辑和传统的 ==
运算不一样,==
运算符会对它两边的操做数作隐式类型转换(若是它们类型不一样),而后才进行相等性比较,(因此才会有相似 "" == false
等于 true
的现象),但 Object.is
不会作这种类型转换。
这与 ===
运算符的断定方式也不同。===
运算符(和==
运算符)将数字值 -0
和 +0
视为相等,并认为 Number.NaN
不等于 NaN
。
若是下列任何一项成立,则两个值相同:
走在最后,欢迎关注:前端瓶子君