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