减少不必要的渲染是提高组件性能的重要一环。
函数事件
可从绑定this方面着手,举几个例子佐证。
jsx中不要定义函数、jsx中不要绑定this
一、直接绑定this
class techCat extends React.Component {constructor(props) {super(props);this.state = {xxx: aaa}}handleClick() {this.setState({xxx: ccc})}render() {return (<div><text>{this.state.xxx}</text><button onClick={this.handleClick.bind(this)}>点我</button></div>)}}缺点: 每次render都会bind,性能不是太好;如果有多个元素都调用这个函数,则会重复bind。虽然实例中定义存储了函数,但是bind方法却会返回一个新的函数,同样加大了内存占用和垃圾回收的开销。可以将函数直接定义为箭头函数,或者在constructor中使用bind改this指向。
二、constructor 手动bind this
class techCat extends React.Component {constructor(props) {super(props);this.state = {xxx: aaa}this.handleClick = this.handleClick.bind(this)}handleClick() {this.setState({xxx: ccc})}render() {return (<div><text>{this.state.xxx}</text><button onClick={this.handleClick}>点我</button></div>)}}优点:相比于第一种性能更好,因为构造函数只执行一次,那么只会 bind 一次,而且如果有多个元素都需要调用这个函数,也不需要重复 bind,基本上解决了第一种的两个缺点。
三、箭头函数类型
class techCat extends React.Component {constructor(props) {super(props);this.state = {xxx: aaa}}handleClick() {this.setState({xxx: ccc})}render() {return (<div><text>{this.state.xxx}</text><button onClick={(e) => {this.handleClick(e)}}>点我</button></div>)}}缺点:每次render都会重复创建函数,性能差。render方法中定义的函数会在每次组件更新中重新定义,每次定义又都会重新申请一块内存,造成更多的内存占用,触发js的垃圾回收也会增大开销,严重影响性能。应该将函数存在实例上,持久化方法和内存,在render中绑定或使用。
四、优化后的箭头函数型
class techCat extends React.Component {constructor(props) {super(props);this.state = {xxx: aaa}}handleClick = () => {this.setState({xxx: ccc})}render() {return (<div><text>{this.state.xxx}</text><button onClick={this.handleClick}>点我</button></div>)}}优点:好看且性能好,没什么缺点
组件粒度细分(合理拆分组件)
通常的写法:class Page extends PureComponent () {constructor(props) {super(props)this.state = {a: 1,...}}render() {const { a } = this.props.data;return <div>{...}</div>}}react的diff比对是以组件为单位进行的,page也是一个大组件。所有的数据都在一个页面,任何一个状态的变化会引起整个页面的刷新。合理地拆分组件, 并且结合PureComponent定义组件,可以减少页面无变化部分的render次数,同时diff比对的粒度更细。优化的写法:class Page extends PureComponent () {constructor(props) {super(props)this.state = {a: 1,b: 2}}render() {const { a } = this.props.data;return <div><Component1 a={a} /><Component2 b={b} />...</div>}}
pureComponent 和 shouldComponentUpdate
在 React 类组件中,可以利用 shouldComponentUpdate 或者 PureComponent 来减少因父组件更新而触发子组件的 render。shouldComponentUpdate 来决定是否组件是否重新渲染,如果不希望组件重新渲染,返回 false 即可。
pureComponent:
先考虑props传递过来的是基本数据类型还是引用类型,若是基本数据则可使用pureComponent,反之Component
pureComponet 通过对 props 和 state 的浅比较结果来实现 shouldComponentUpdate,当对象包含复杂的数据结构时,可能就不灵了,对象深层的数据已改变却没有触发 render
看下shallowEqual 是如何实现浅比较的
function shallowEqual(objA: mixed, objB: mixed): boolean {// 首先对基本类型进行比较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);// 长度不相等直接返回falseif (keysA.length !== keysB.length) {return false;}// key相等的情况下,再去循环比较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;}
使用 React.memo
React.memo 是 React 16.6 新的一个 API,用来缓存组件的渲染,避免不必要的更新,其实也是一个高阶组件,与 PureComponent 十分类似,但不同的是, React.memo 只能用于函数组件。
基本用法import { memo } from 'react';function Button(props) {// Component code}export default memo(Button);高级用法默认情况下其只会对 props 做浅层对比,遇到层级比较深的复杂对象时,表示力不从心了。对于特定的业务场景,可能需要类似 shouldComponentUpdate 这样的 API,这时通过 memo 的第二个参数来实现:function arePropsEqual(prevProps, nextProps) {// your codereturn prevProps === nextProps;}export default memo(Button, arePropsEqual);注意:与 shouldComponentUpdate 不同的是:arePropsEqual 返回 true 时,不会触发 render,如果返回 false,则会。而 shouldComponentUpdate 刚好与其相反。
React.Fragment
React.Fragment 组件能够在不额外创建 DOM 元素的情况下,让 render() 方法中返回多个元素。
render() {return (<React.Fragment>Some text.<h2>A heading</h2></React.Fragment>);}
