在平时的项目开发中,性能问题是比较大的问题,小型项目中还看不出来太大的变化 ,当项目的规模达到一定程度时,性能问题就显得重要,下面分别来介绍几种性能优化的方法。

  一、Fragment

  fragment可以聚合一个子元素列表,并不需要在DOM中增加额外的节点 ,避免多写一层view,多层级的嵌套。React.Fragment看起来像是空的JSX标签。

return (
    <React.Fragment>
        <Component />
    </React.Fragment>
)
//Fragment短语法
return (
    <>
        <Component />
    </>
) 

  二、shouldComponentUpdate

  组件更新生命周期中的shouldComponentUpdate(SCU)从字面上来理解它问的是是否需要进行更新,默认返回的true进行更新。但在组件渲染的过程中,有时候并没有用到props/state或者是在父组件重新渲染时子组件的props/state并没有发生改变,这时render得到的是和之前一样的虚拟DOM,所以我们要使用SCU来进行组件是否需要更新的判断。通过这个API我们可以拿到改变前后的props/state,手动的检查状态是否发生了更新,再根据实际的变量情况决定是否需要进行重新渲染。

import React from 'react'
import reactDOM from 'react-dom'
class Counter extends React.Component {
    render() {
        console.log('Counter render')
        return <div>当前总和是:{this.props.count}</div>
    }
}
class App extends React.Component {
    constructor(props) {
        super(props);
        this.inputRef = React.createRef();
        this.state = { count: 0 };
    }
    render() {
        console.log('App render')
        return (
            <div>
                <Counter count={this.state.count} />
                <input ref={this.inputRef} />
                <button onClick={this.add} >+</button>
            </div>)
    }
    add = () => {this.setState({ count: this.state.count + parseInt(this.inputRef.current.value) })}
    //shouldComponentUpdate有两个参数:nextProps,nextState.
    //用上一次的props/state和这一次的props/state进行比较,如果相同返回false 不需要更新,如果不一样,则更新
    shouldComponentUpdate(nextProps, nextState) {
        // return true;//默认值
        if (this.state.count !== nextState.count) {
            return true;
        }
        return false;
    }

}
reactDOM.render(<App />, document.getElementById('root'))

  二、PureComponent

  如果多处要用到SCU时,react中还有一个类似的组件PureComponent,在组件更新对props/state进行一次浅比较来判断是否进行更新操作实现了SCU。在项目中,如果定义了SCU,无论组件是否是PureComponent,它都会执行SCU来判断是否进行更新,如果组件中没有SCU,那就会判断这个组件是否是PureComponent,是的话会对新旧props/state进行浅比较,不一致的话则会触发更新操作。

import React from 'react'
import reactDOM from 'react-dom'
class Counter extends React.PureComponent {
    render() {
        console.log('Counter render')
        return <div>当前总和是:{this.props.count}</div>
    }
}
class App extends React.PureComponent {
    constructor(props) {
        super(props);
        this.inputRef = React.createRef();
        this.state = { count: 0 };
    }
    render() {
        console.log('App render')
        return (
            <div>
                <Counter count={this.state.count} />
                <input ref={this.inputRef} />
                <button onClick={this.add} >+</button>
            </div>)
    }
    add = () => { this.setState({ count: this.state.count + parseInt(this.inputRef.current.value) }) }
    //shouldComponentUpdate有两个参数:nextProps,nextState.
    //用上一次的props/state和这一次的props/state进行比较,如果相同返回false 不需要更新,如果不一样,则更新
}
reactDOM.render(<App />, document.getElementById('root')

  浅比较就是对栈内存中的数据进行比较,我们手写一个PureComponent,从而来更好的理解浅比较。当参数为基本数据类型或者是同一个引用对象那直接返回true;判断两个不同引用类型对象是否相同,先比较其length属性,如这一步不同,则返回false;再通过Object.keys获取对象的属性进行比较。

import React, { Component, createRef } from 'react'
import ReactDOM from 'react-dom';
class PureComponent extends React.Component {
    shouldComponentUpdate(newProps) {//判断是否需要更新
        return !shallowEqual(this.props, newProps);
    }
}
//浅比较,只比较第一层
function shallowEqual(obj1, obj2) {
    if (obj1 === obj2) {//obj1和obj2引用地址完全相等的情况
        return true;
    }
    if (typeof obj1 != 'object' || obj1 == null || typeof obj2 != 'object' || obj2 == null) { //判断两个数据都为object的情况
        return false;
    }
    let key1 = Object.keys(obj1);
    let key2 = Object.keys(obj2)
    if (key1.length != key2.length) {//属性数量不一样,直接返回false
        return false;
    }
    //没有进行递归操作,它不是深比较,只是比较一层而已
    for (let key of key1) { //循环obj1属性的数组,将其和obj2进行比较
        if (!obj2.hasOwnProperty(key) || obj1[key] != obj2[key]) {//如果obj2没有这个key,或者是obj1的key的值不等于obj2的key的值
            return false
        }
    }
    return true;//最后返回true  
}
class Counter extends PureComponent {
    render() {
        console.log('Counter render')
        return <p>当前的总和是:{this.props.counter.count}</p>
    }
}
class App extends Component {
    constructor(props) {
        super();
        this.inputRef = createRef();
        this.state = { counter: { count: 0 } }
    }
    add = () => {
        let oldState = this.state;
        let amount = parseInt(this.inputRef.current.value);
        //这里的判断如果amount==0,那counter还是老的counter对象没有改变
        let newState = { ...oldState, counter: amount == 0 ? oldState.counter : { count: oldState.counter.count + amount } };
        this.setState(newState);
    }

    render() {
        console.log('App render')
        return (
            <div>
                <Counter counter={this.state.counter} />
                <input ref={this.inputRef} />
                <button onClick={this.add} >+</button>
            </div>)
    }
}
ReactDOM.render(<App />, document.getElementById('root'))

  三、immutable.js

  在使用PureComponent时,它只是进行了浅层比较,如果props传入的对象嵌套的层级太多,可能会引起props或者是state引用地址未发生变化从而导致shouldComponentUpdate返回false,未触发render函数,没有渲染组件的情况,这时我们就可以用到immutable.js库来进行优化。

  immutable.js是一个持久性数据结构的库。Immutable Date 是一旦创建就不能再被更改的数据,对immutable对象的任何修改或添加删除操作都会返回一个新的immutable对象。它在使用旧数据创建新数据时,要保证旧数据可用且不变,还要避免deepCopy深拷贝带来的性能上的大量损耗,所以immutable使用了结构共享,即如果对象树中一个节点发生变化,只修改这个节点和受它影响的父节点,其它节点进行共享。

  Immutable.js常用的API有以下几个is()、Map()、List()等等,下面来介绍几个常用的API

  is(map1,map2): 对map1和map2进行比较。和js中对象的比较不同,在js中比较两个对象比较的是地址,但是在immutable中比较的是这个对象hashCode和valueOf,只要两个对象的hashCode相等,值就是相同的,避免了深度遍历,提高了性能。

import { Map, is } from 'immutable'
let map1 = Map({ a: { aa: 1 }, b: 2, c: 3 });
let map2 = Map({ a: { aa: 1 }, b: 2, c: 3 });
console.log(map1 === map2);//false
Object.is(map1, map2); //false
is(map1, map2);//true

  Map():用来创建一个新的Map对象;get():获取值 ;set():设置值

let { Map} = require('immutable');
let map1 = Map({ a: { aa: 1 }, b: 2, c: 3 });
let map2 = map1.set('b', 50);
console.log(map1.b, map2.b);//这是不一样的,map2中的b进行了修改
console.log(map1.get('a') === map2.get('a'));//true 这是一样的,a的值它们是进行共享的如何将immutable.js和PureComponent结合起来使用。

  把immutable.js用到项目中,代码如下:

import React, { Component, createRef,PureComponent } from 'react'
import ReactDOM from 'react-dom';
import {Map} from 'immutable'
class Counter extends PureComponent {
    render() {
        console.log('Counter render')
        //用get取值
        return <p>{this.props.counter.get('count')}</p>
    }
}
class App extends Component {
    constructor(props) {
        super();
        this.inputRef = createRef();
        //使用Map
        this.state = { counter: Map({ count: 0 }) }
    }
    add = () => {
       //这样的话就要用到immutable.js这个库
       let oldState = this.state;
       let amount = parseInt(this.inputRef.current.value);
       oldState.counter = oldState.counter.set('count', oldState.counter.get('count') + amount)//每次set后会返回一个新的对象赋值给oldState.counter
       this.setState(oldState)
    }
    render() {
        console.log('App render')
        return (
            <div>
                <Counter counter={this.state.counter} />
                <input ref={this.inputRef} />
                <button onClick={this.add} >+</button>
            </div>)
    }
}
ReactDOM.render(<App />, document.getElementById('root'))

   四、memo

  上面是类组件的优化,那函数组件呢?函数组件采用React.memo()进行优化提高组件性能。React.memo是一个高阶组件,它仅检查props的变化,如果组件在相同的props的情况下,那可以通过将组件包裹在React.memo中调用,以此通过记忆组件渲染结果的方式来提高组件的性能表现。这意味着在这种情况下,React 将跳过渲染组件的操作并直接复用最近一次渲染的结果。在默认情况下它和PureComponent一样都是进行浅比较的。

import React, { Component, createRef,  PureComponent } from 'react'
import ReactDOM from 'react-dom'
//函数组件
function Count(props) {
    console.log('Count render')
    return <p>当前数的总和是:{props.count.count}</p>
}
//使用memo
// let MemoCount = React.memo(Count);

//重写memo
let MemoCount = memo(Count);
function memo(FuctionComponent){
    return class extends PureComponent{
        render(){
            return <FuctionComponent {...this.props} />
        }
    }
}
 class App extends Component {
    constructor(props) {
        super();
        this.inputRef = createRef();
        this.state = {
            count: { count: 0 },
        }
    }
    add = () => {
        let oldState = this.state;
        let amount = parseInt(this.inputRef.current.value);
        let newState = { ...oldState, count: amount == 0 ? oldState.count : { count: oldState.count.count + amount } };
        this.setState(newState)
    }
    render() {
        console.log('App render')
        return (
            <div>
                <MemoCount count={this.state.count} />
                <input ref={this.inputRef} />
                <button onClick={this.add} >+</button>
            </div>)
    }
}
ReactDOM.render(<App />, document.getElementById('root'))

 

  

  

02-14 02:42