react 的组件优化

117 阅读5分钟

组件优化

分类

  • 父组件更新导致子组件渲染
  • Props的错误写法导致组件渲染
  • Context的更新导致组件渲染

原理

组件性能优化,一般就是优化子组件,优化子组件的不相关的 render(渲染)

思路

避免重新渲染的思想是对比Props的引用:

  1. 父组件更新会更新子组件的引用

    1. 使用PureComponent,memo,useMemo,children(不推荐)
  2. 子组件直接传递引用类型,每次的props引用也是新的

    1. 使用变量定义引用类型,再进行传递,变量的引用是不会变得,除非内部的值变了——针对类组件和函数的useState定义的状态变量

        state = {
            count: 0,
            pass: { a: 1 },
          }
           handleClick = () => {
            console.log('handleClick')
            // 只要父组件调用的方法会改变要传给子组件的值,哪怕是一模一样的也会重新渲染子组件,因为引用变了,所以这种情况只是适用于值不变的情况
            // this.setState({ count: ++this.state.count, pass: { a: 2, b: 3 } })
            this.setState({ count: ++this.state.count, pass:{a:1} })
          }
        ```
      
      
    2. useMemo,useCallback——函数组件的解决办法

      1. useMemo缓存值
      2. useCallback缓存函数

父组件更新导致子组件渲染

1. class 组件

  1. shouldComponentUpdate( state, props ) // 基于传入的 props 做判断

  2. PureComponent——就是React会自动帮我们执行 shouldComponentUpdateProps进行浅比较优化更新。

    父组件中的state发生变化,执行了React.createElement(Son) ,所得到的的props的引用每次都是新的,因此会引发重新渲染。【正常,但是要避免】

  3. children方式 ——不推荐

2. 函数组件

Function组件 的特性是每一次的组件重新渲染,都会重新执行一次函数。而对于Class组件来说,只会执行一遍 new Class ,其实仔细想想还是挺可怕的。对函数组价来说,每次的执行都意味着新的上下文,新的变量,新的作用域。因此我们要更加注重函数组件的性能优化。

  1. memo() 包裹子组件

    1. memo这个api, 主要是对比props引用是否改变,从而避免子组件的重新渲染
  2. useMemo 计算逻辑优化,

    1. 可以包裹子组件,对子组件进行优化,第二个参数为传依赖数组;也可以用于缓存值

    2. 作者力推:useMemo 保存了组件的引用,没有重新执行函数组件,因此避免了组件内的变量,函数声明,和作用域的声明。从而优化了性能

  3. children——不建议

// 测试时的代码——可以移步参考文献(更详细)
import React, { Component, PureComponent, useState, useEffect, useMemo, memo } from 'react'
import { Button } from 'antd'// 父组件为函数组件
// const Detail = () => {
//   const [count, setCount] = useState(0)
//   useEffect(() => {
//     console.log(window.location, 'window.location')
//   }, [])
//   return (
//     <div>
//       <Button onClick={() => setCount((preData) => ++preData)}>点击</Button>
//       <div>{count}</div>
//       {/* 第一个元素是要缓存的组件,第二个是依赖数组 */}
//       {useMemo(
//         () => (
//           <UseMemoChild />
//         ),
//         []
//       )}
//       <MemoChild/>
//       <Child />
//     </div>
//   )
// }
// 父组件为类组件
class Detail extends Component {
  state = {
    count: 0,
  }
  render(): React.ReactNode {
    return (
      <div>
        <Button onClick={() => this.setState({ count: ++this.state.count })}>点击</Button>
        <div>{this.state.count}</div>
        <Child />
        <MemoChild />
        <ClassChild />
        <PureClassChild />
      </div>
    )
  }
}
​
// 错误
const Child = () => {
  useEffect(() => {
    console.log('Child')
  })
  return <div>Child</div>
}
// 正确
const MemoChild = memo(() => {
  useEffect(() => {
    console.log('MemoChild')
  })
  return <div>Child</div>
})
// 正确——并且只能在函数中使用
const UseMemoChild = () => {
  useEffect(() => {
    console.log('UseMemoChild')
  })
  return <div>Child</div>
}
// 错误
class ClassChild extends Component {
  componentDidUpdate() {
    console.log('ClassChild')
  }
  render(): React.ReactNode {
    return <div>ClassChild</div>
  }
}
// 正确——并且只能在类中使用
class PureClassChild extends PureComponent {
  componentDidUpdate() {
    console.log('PureClassChild')
  }
  render(): React.ReactNode {
    return <div>ClassChild</div>
  }
}
​
export default Detail

props引起的子组件渲染

  1. 错误写法——禁止这样写

    这个解决办法只有类组件和函数组件的useState定义的变量生效

<MemoChild count={{ a: 1 }} handleOk={() => {}} />

为什么会引起渲染?因为组件的渲染主要是通过监听props和state的变化进行渲染的,在这里props每次都是一个新的对象,因为引用的不同,每次父组件的渲染都会导致子组件的渲染

定义好变量然后再传

 <MemoChild count={this.state.pass} handleOk={this.handleClick} />

直接将变量传给子组件,因为变量的引用不修改的话是不会变得,因此props就是没有变化的,也就不会重新渲染子组件了。

但是如果变量内部变了,也是会引发渲染的(要不然子组件怎么渲染啊!!!),但避免了值没变就渲染子组件,浪费性能。

  1. 可以使用的优化——不要过度使用

    函数组件可以生效的办法

使用了 useCallback && useMemo 这个两个优化Hook,主要就是根据依赖是否改变来确定是否要更新值的变化,以保证值的引用不变。这中写法适合于大部分的写法

 const anyMethod = useCallback(() => {}, []);
​
  const [componentDetails] = useMemo(() => {
    const componentDetails = { name: "子组件" };
    return [componentDetails];
  }, []);

函数组件的优化和类组件有什么不同

函数组件每次更新都会执行所有的代码

 // 函数每次更新都会重新执行所有的代码,(只有useState中的状态会保留)不像类组件只执行一次,类组件就不会重新执行下面的代码
  console.log('重新执行函数的代码了')
  // 直接定义然后传值的话,
  const pass2 = { a: 1 }

但类组件不会重新挂载,只是触发更新而已

 componentDidMount() {
    // 不会重新挂载
    console.log('重新执行函数的代码了---mounted')
  }
  componentDidUpdate() {
    console.log('重新执行函数的代码了---update')
  }

因此函数组件的优化更重要

Function组件 的特性是每一次的组件重新渲染,都会重新执行一次函数。而对于Class组件来说,只会执行一遍 new Class ,其实仔细想想还是挺可怕的。对函数组价来说,每次的执行都意味着新的上下文,新的变量,新的作用域。因此我们要更加注重函数组件的性能优化。

函数组件要优化,必须使用useMemo,useCallback进行优化

参考文献:juejin.cn/post/702317…