Lirx-Xin / LirxdeBlog

blog记录
0 stars 0 forks source link

React函数式组件&&React Hook开发中的性能优化 #9

Open Lirx-Xin opened 3 years ago

Lirx-Xin commented 3 years ago

React函数式组件&&React Hook开发中的性能优化


  React组件代码中的优化思路主要是减少render渲染函数的重复运行,因为React中父组件更新,所有子组件也会重新渲染。   在类组件中可通过在shouldComponentUpdate生命周期中判断更新后的prop或state与更新前的prop或state是否有更新来决定是否重新渲染,返回false不重新渲染。也可以通过继承PureComponent组件来使该组件自身拥有判断prop(或state)决定是否重新渲染的能力,不过该方式只提供一个浅比较的方法(即数组与对象类型的prop只比较其在内存中的引用地址,即便其内部值改变了,也不会触发更新),该方式也会导致其子组件在触发更新时,浅比较下该更新的不更新,即使其子组件是继承自Component类。

那么在函数式组件中没有生命周期也没有类,那么该如何进行这方面的优化呢

  1. React.memo

    该API是对标类组件中的PureComponent

    //app组件
    import React,{useState} from 'react'
    import Child from './child'
    export default function App(){
    let [name,setName] = useState('nihao')
    return (
        <div>
            <div>{name}</div>
            <button onClick={() => setName('name被修改')}>修改名字</button>
            <Child name="不变"></Child>
        </div>
    )
    }
    // child组件
    export default function Child(props){
    console.log(props.name)
    return (
    <div>{props.name}</div>
    )
    }

      首次渲染时,控制台会打印不变,说明child组件已经渲染。当我们点击修改按钮时,name改变,此时控制台会再次打印不变,说明Child函数再次执行,该组件再次渲染,但是该组件并未有状态的更改,此次的执行渲染自然是额外的性能开销,那要怎样才能不让他渲染呢,这里就可以使用React.memo;   React.memo其实是一个高阶组件,它会将我们的组件进行一次包装,返回一个可缓存状态的组件。

    // 使用React.memo的Child组件
    function Child(props){
    console.log(props.name)
    return (
    <div>{props.name}</div>
    )
    }
    export default React.memo(Child)

      此时再点击修改名字按钮,就会发现控制台不会再打印不变,因为Child组件没有更新就不会重新渲染。   但是这样的React.memo作用与PureComponent的作用类似,他们只进行一个浅比较的方法。不过React.memo还提供了一个回调,让我们自定义其状态属性的比较方法。

    // Child组件
    function Child(props){
    console.log(props.name)
    return (
    <div>{props.name}</div>
    )
    }
    function areEquel(prevProps,nextProps){
    // 对props对象进行比较,相等返回true,不等返回false,与shouldComponentUpdate返回结果相反
    }
    export default React.memo(Child,areEquel)
  2. useCallback

    将上方的示例代码进行更改:

    //app组件
    import React,{useState} from 'react'
    import Child from './child'
    export default function App(){
    let [name,setName] = useState('nihao')
    const cb = () => {
        setName('自己修改name')
    }
    return (
        <div>
            <div>{name}</div>
            <button onClick={() => setName('name被修改')}>修改名字</button>
            <Child onClick={cb} name="不变"></Child>
        </div>
    )
    }
    // child组件
    function Child(props){
    console.log(props.name)
    return (
    <div>
    <button onClick={props.onClick}>自己修改</button>
        <div>{props.name}</div>
    </div>
    )
    }
    export default React.memo(Child)

      此时当我们点击修改名字时,控制台又会打印出不变,这是为什么呢,传递给Child组件的props看起来是没有改变,但其实当App组件进行重新渲染时,该函数组件会再次执行,也就是再次创建了一个cb函数,与其更新之前的cb函数,在内存中的引用地址其实是不一样的,所以Child组件触发了刷新。   此时,我们就可以使用useCallback:

    //app组件
    import React,{useState,useCallback} from 'react'
    import Child from './child'
    export default function App(){
    let [name,setName] = useState('nihao')
    const cb = () => {
        setName('自己修改name')
    }
    const memoziedcb = useCallback(cb,[])
    return (
        <div>
            <div>{name}</div>
            <button onClick={() => setName('name被修改')}>修改名字</button>
            <Child onClick={memoziedcb} name="不变"></Child>
        </div>
    )
    }
    // child组件
    function Child(props){
    console.log(props.name)
    return (
    <div>
    <button onClick={props.onClick}>自己修改</button>
        <div>{props.name}</div>
    </div>
    )
    }
    export default React.memo(Child)

      此时再次触发修改,Child组件不会再次重新渲染。   将函数及依赖项作为参数传递给useCallback,useCallback会返回该函数的memoized,返回的这个函数,只有在依赖项改变时,才会更新。   如果我们的 callback 传递了参数,当参数变化的时候需要让它重新添加一个缓存,可以将参数放在 useCallback 第二个参数的数组中,作为依赖的形式,使用方式跟 useEffect 类似。

  3. useMemo

    useMemo的作用与React.memouseCallback的作用不一样,useMemo主要是为了减少计算量:

    function App(){
    let [count,setCount] = useState(0)
    function expensiveFn{
        // 复杂计算结果并返回
        console.log(num)
        return num
    }
    const num = expensiveFn()
    return (
        <div>
            <h5>{count}</h5>
            <button onClick={() => setCount(count+num)}>增加</button>
        </div>
    )
    }

    上面实例中,每次点击按钮都会对函数expensiveFn进行计算,而expensiveFn返回的值是没有改变的,所以这样无疑是多余的性能开销。此时我们就可以使用useMemo来进行优化:

    function App(){
    let [count,setCount] = useState(0)
    function expensiveFn{
        // 复杂计算结果并返回
        console.log(num)
        return num
    }
    const num = useMemo(expensiveFn,[])
    return (
        <div>
            <h5>{count}</h5>
            <button onClick={() => setCount(count+num)}>增加</button>
        </div>
    )
    }

     此时,我们再点击按钮时,我们会发现,打印的num值只有第一次会执行;  注意:如果在使用useMemo时,我们没有传递依赖值,函数会每次都执行。对于一些计算量较少的计算,可以不用useMemo,也不会有什么太大收益。