18条React的组件性能优化整理

1,367 阅读9分钟

前面整理了Vue开发组件和性能优化方案,需要的去看看:

18条常用好用的Vue项目代码和性能优化方案整理 |求关注😘,求点赞😘,鼓励一下😘

组件性能优化核心是减少真实DOM节点的渲染频率,减少虚拟DOM的对比频率

1、组件在卸载之前一定要进行清理操作

  • 在组件中为window注册的全局事件以及定时器,在组件卸载前要清理掉,防止组件卸载后继续执行影响性能。
  • componentWillUnmount中处理
componentWillUnmount() {
    //清理定时器
    clearTimeout/clearInterval(this.timer);
    //清理滚动监听
    window.onScroll=null;
    //清理监听事件
    document.removeEventListener("click", this.closeMenu); 
}

2、类组件继承PureComponent类实现对属性和状态用浅比较

  • 什么是纯组件 纯组件会对组件输入数据进行浅层比较,如果当前输入数据和上次输入数据相同,组件不会重新渲染
  • 什么是浅层比较 比较引用数据类型在内存中的引用地址是否相同,比较基本数据类型的值是否相同
  • 如何实现纯组件 类组件继承PureComponent类,函数组件使用memo方法
  • 为什么不直接进行diff操作,而是要先进行浅层比较,浅层比较难到没有性能消耗吗? 和进行diff比较操作相比,浅层比较将消耗更少的性能。diff操作会重新遍历整颗virtualDOM树,而浅层比较只操作当前组件的state和props。
  • component和PureComponent比较: PureComponent自带通过prop是和state的浅对比实现shouldComponentUpdate(),而Component没有
  • PureComponent缺点: 可能因深层的数据不一致而产生错误的否定判断,从而shouldComponentUpdate结果返回false,界面得不到更新。
  • PureComponent优势 不需要开发者自己实现shouldComponentUpdate,就可以进行简单的判断,提升性能, 减少ES6的类组件的无用渲染。
import React, { PureComponent } from 'react';

class App extends PureComponent {}
export default App;

3、shouldComponentUpdate生命周期钩子,类组件页面更新时调用

  • 纯组件只能进行浅层比较,要进行深层比较需使用shouldComponentUpdate编写自定义比较逻辑
  • 返回true重新渲染组件,返回false阻止重新渲染
  • 函数两个参数nextProps是更新之后组件的props,nextState是更新后的组件的自身的state
class CounterButton extends React.Component {
    constructor(props) {
        super(props);
        this.state = {count: 1};
    }

    shouldComponentUpdate(nextProps, nextState) {
        if (this.props.color !== nextProps.color) {
            return true;
        }
        if (this.state.count !== nextState.count) {
            return true;
        }
        return false;
    }

    render() {
        return (
          <button
            color={this.props.color}
            onClick={() => this.setState(state => ({count: state.count + 1}))}>
            Count: {this.state.count}
          </button>
        );
    }
}

4、React.memo 函数组件

  • React.memo是一个高阶组件,接受一个组件作为参数返回一个新的组件
  • 将函数组件变为纯组件,将当前props和上一层的props进行浅层比较,如果相同就阻止组件重新渲染
memo()
  • memo传递比较函数,比较逻辑 使用memo方法自定义比较逻辑,用于执行深层比较 比较函数的第一个参数为上一次的props,比较函数的第二个参数为下一次的props,比较函数返回true,不进行渲染,比较函数返回false,组件重新渲染。
function comparePerson(prevProps, prevProps){
    if(prevProps.person.name!==prevProps.person.name){
            return false;
    }
    return true;
}
const ShowPersonMemo = memo(ShowPerson,comparePerson)
<ShowPersonMemo person = {person}/>

5、使用组件懒加载

  • 不使用会把所有文件打包在一个文件,文件很大,加载很慢
  • 使用组件懒加载可以减少bundle文件大小,加快组件呈递速度。
  • 访问哪个页面,加载哪个页面
import {lazy,Suspense} from ‘react’;
const Home = lazy(()=>import(/*webpackChunkName:”home”*/'./Home'))
const About = lazy(()=>import(/*webpackChunkName:”about”*/'./About'))
Import Loading from './Loading'

路由最外侧要包裹一个<BrowserRouter><App/></BrowserRouter>或者<HashRouter><App/></HashRouter>,管理整个app的路由

Switch的使用:

  • 包裹路由,通常情况下,path和component是一一对应的关系。
  • Switch可以提高路由匹配效率(单一匹配)
<BrowserRouter>
    <Switch>
        <Suspense fallback={<Loading/>}> //备用
             <Route path=‘/XXX’ component={Dome}/>
        </Suspense>
    </Switch>
</BrowserRouter>

根据条件进行组件懒加载,适用于组件不会随条件频繁切换。

const LazyComponent = nullif(true){
    LazyComponent = lazy(()=>import(/*webpackChunkName:”home”*/‘./Home’))
}else{
    LazyComponent = lazy(()=>import(/*webpackChunkName:”about”*/‘./About’))
}

<Suspense fallback={<Loading/>}> //fallback备用组件
    <LazyComponent />
</Suspense>

6、使用Fragment避免额外加载

  • React组件中返回的jsx,如果有多个同级元素,多个同级元素必须要有一个共同的父级
  • <Fragment></Fragment>或者语法糖<></>它只是占位,不渲染,在DOM中不增加额外的节点
  • <></>不能接受键值或属性

7、不要使用内联函数定义

  • 在使用内敛函数后,render方法每次运行都会创建新的函数实例,导致React在进行Virtual DOM对比时,新旧函数对比不相等,导致React总是为元素绑定新的函数实例,而旧的函数实例又要交给垃圾回收器处理。
  • 组件中单独定义函数,将函数绑定给事件。

8、在构造函数中进行函数this绑定

  • 在类组件中如果使用fn(){}这种方式定义事件函数,事件函数this默认指向undefined,也就是函数内部的this指向需要被更正
  • 可以在构造函数中对函数的this进行更正,也可以在行内进行更正,两者看起来没区别,但是对性能的影响是不同的
constructor(){
    super()
    //更正this指向,只执行一次
    this.handleClick = this.handleClick.bind(this)
}

render(){
    //render方法每次执行都会生成新的函数实例
    return <button onClick={this.handleClick.bind(this)}></button>
}

9、类组件中的箭头函数

  • 在类组件中使用箭头函数不会存在this指向问题,因为箭头函数本身不绑定this
  • 箭头函数在this指向上占据优势,但同时也有不利的一面: 当使用箭头函数时,该函数被添加为类的实例对象属性,而不是原型对象属性,如果组件被多次重用,每个组件实例对象中都将会有一个相同的函数实例,降低了函数实例的可重用性造成资源浪费。
  • 更正函数内部的this指向的最佳方法是在构造函数中使用bind方法进行绑定

10、避免使用内联样式

  • 当使用内联style为元素添加样式时,内联样式会被编译为JavaScript代码,通过JavaScript代码将样式规则映射到元素的身上,浏览器就会花费更多的时间执行脚本和渲染UI,从而增加了组件的渲染时间。

  • 更好的办法:将css文件导入样式组件,通过css直接做的事情就不要通过JavaScript去做,因为JavaScript操作DOM非常慢

11、优化条件渲染

  • 频繁的挂载和卸载组件时一项耗性能的操作,应该减少组件挂载和卸载的次数
  • 在React中我们经常会根据条件渲染不同的组件,条件渲染师一项必须做的优化操作
{true&&<List/>}

12、避免重复无限渲染

  • 当应用程序状态发生更改时,React会调用render方法,如果在render方法中继续更改应用程序状态,就会发生render方法递归调用导致应用报错
render(){
    this.setState({name:’zzz’})
    return <div>{this.state.name}</div>
}

与其他生命周期函数不同,render方法应该被作为纯函数,这意味着在render方法中不要做以下事情

  • 不要调用setState方法
  • 不要使用其他手段查询更改原生DOM元素,以及其他更改应用程序的任何操作,render方法的执行要根据状态的执行,这样可以保持组件的行为和渲染方式一致。

13、为组件创建错误边界

  • 默认情况下,组件渲染错误会导致整个应用程序中断,创建错误边界可确保在特定组件发生错误时应用程序不会中断。
  • 错误边界是一个React组件,可以捕获子级组件在渲染时发生的错误,当错误发生时,可以将错误记录下来,可以显示备用UI界面。
  • 错误边界涉及到两个生命周期函数:getDerivedStateFromErrorcomponentDidCatch
  • getDerivedStateFromError
    1. 该函数为静态函数 static getDerivedStateFromError
    2. 运行时间点:子组件被渲染发生错误之后且页面更新之前
    3. 只有子组件发生错误时,该函数才会被执行
    4. 该函数返回一个对象,React会将该对象的属性覆盖掉当前组件的state
    5. 参数:错误对象
    6. 通常该函数用于更改应用程序的状态
class ErrorBound extends React.Component {
    this.state={
        hasError:false
    }
   //   该函数为静态函数
    static getDerivedStateFromError(error){
        return {     // 返回的值会自动 调用setState,将值和state合并
            hasError:true
        }
    }
    render(){
        if(this.state.hasError){
            return null
        }
        return this.props.children
    }
}
  • componentDidCatch方法用于记录应用程序的错误信息
    1. 实例方法,该方法的参数就是错误对象
    2. 运行时间点:子组件渲染发生错误且在页面更新之后;由于该函数的运行时间点比较靠后,因此不会在该函数中改变状态
    3. 通常该函数用于记录错误信息

14、避免数据结构突变

  • 组件中propsstate的数据结构应该保持一致,数据结构突变会导致输出不一致

15、为列表数据添加唯一标识key

  • 可以避免因元素变化而导致重新创建
  • 动态列表(排序、过滤、从中间或者顶部删除item)不建议用索引作为key

16、依赖优化

$ npm I react-app-rewired customize-cra —save-dev

17、高阶组件是类组件之间共享逻辑,类中使用较多

1、什么是高阶组件 Higher Order Component (HOC)

  • 高阶组件是React应用中共享代码、增加逻辑复用的一种方式,比如A组件和B组件都需要使用一个相同的逻辑。可以使用高阶组件将逻辑抽取到一个公共的地方使用。
  • 高阶组件的核心思想就是在组件的外层再包裹一层执行逻辑的组件,在外层组件中执行逻辑,再将逻辑执行的结果传递到内容组件。
  • 高阶组件由一个函数返回,函数接受组件作为参数,返回一个新的组件,参数组件就是要服用的组件,函数内部返回的新组件就是执行逻辑的组件,在新组件内部执行完逻辑以后再调用参数组件并将逻辑结果传递给参数组件。

18、渲染属性和Portal

渲染属性类中使用较多 Portal将元素渲染到任何根元素