react_hooks系列 useCallback,高阶函数memo

发布时间 2023-12-12 22:32:50作者: 成全我的理想

react_hooks的useCallback,高阶函数memo
一、概念和作用
1、memo高阶函数:
memo解决的是函数式组件的无效渲染问题,当函数式组件重新渲染时,会先判断数据是否发生了变化。相当于类组件的PureComponent(默认提供ShouldComponentUpdate)

2、useCallback:

1)、useCallback会返回一个函数的memoized(记忆的)值

2)、在依赖不变的情况下,多次定义(如:函数)的时候,返回的值是相同的 。

3)、格式:let 新的函数 = useCallback(曾经的函数, [依赖的值])

 

二、使用场景:
1、memo高阶函数的使用场景:
不论父组件是什么类型的组件,子组件是否渲染 :

1)、 如果子组件是类组件(继承自PureComponent)
​ 那么是否渲染由props和state是否改变决定;

2)、如果子组件是函数式组件
​ 只要父组件渲染,子组件会无条件渲染。如下是代码示例:

//父组件:
import { useState } from "react";
import SonFn from "./SonFn";

export default () => {
    console.log("父组件");
    const [count, setCount] = useState(1);

    let changeCount = () => {
        setCount(count + 1);
   }
    return (
        <>
            <h1>useCallback</h1>
            <p>{count}</p>
            <input type="button" value="修改count" onClick={changeCount} />
            <hr />
            <SonFn />
        </>
    )
}
//子组件:
./SonFn.js

export default ()=>{
    console.log("子组件");
    return (
        <div>
            <h5>子组件(函数式组件)</h5>
        </div>
    )
}   

只要点击按钮"修改count”,父组件就会刷新,而子组件SonFn也会无条件渲染(这是无效的渲染)。

3)、解决方案:
​ 把子组件用高阶函数memo进行包裹,就能解决子组件的无条件渲染问题,即:子组件的渲染就会由props和state决定,有点像类组件继承自PureComponent的感觉。

如下是代码(只需要把子组件的代码进行修改就行):

//子组件:
import React,{memo} from 'react'

const SonFn = ()=>{
    console.log("子组件");
    return (
        <div>
            <h5>子组件(函数式组件)</h5>
        </div>
    )
}

export default memo(SonFn);
2、useCallback的使用场景:

父组件是函数式组件,子组件也是函数式组件(并且用memo包裹)

1)、子组件的属性是数据:

​ 如果数据不变化,那么子组件不渲染,如果数据发生变化,那么子组件渲染。这里就没有性能问题。

//父组件

import { useState,useCallback } from "react";
import SonFn from "./SonFn";

export default () => {
    console.log("父组件UseCallback");
    const [count, setCount] = useState(1);

    let changeCount = () => {
        setCount(count + 1);
    }

    return (
        <>
            <h1>useCallback1</h1>
            <p>{count}</p>
            <input type="button" value="修改count" onClick={changeCount} />
            <hr/>
            {/*此处给子组件传入了数据count,count只要发生变化,子组件就会重新渲染*/}
            <SonFn count={count} />
        </>
    )
}

//子组件:
./SonFn.js

import React,{memo} from 'react'

const SonFn = ({count})=>{
    console.log("子组件");
    return (
        <div>
            <h5>子组件(函数式组件)</h5>
            <p>{count}</p>
        </div>
    )
}

export default memo(SonFn);
2)、子组件的属性是函数时,就会出现问题:

​ 父组件刷新了,子组件依然会刷新。因为,父组件(函数式)每次刷新时,函数都会重新定义,那么传给子组件的函数属性必然会发生变化。所以子组件会刷新,如下是示例代码:

//父组件:

import { useState } from "react";
import SonFn from "./SonFn";

export default () => {
    console.log("父组件");
    const [count, setCount] = useState(1);
    let changeCount = () => {
        setCount(count + 1);
    }
    let increment = ()=>{
        console.log("increment");
    }
    return (
        <>
            <h1>useCallback</h1>
            <p>{count}</p>
            <input type="button" value="修改count" onClick={changeCount} />
            <hr/>
            <SonFn onMyClick={increment} />
        </>
    )
}

//子组件:
./SonFn.js

import React,{memo} from 'react'
const SonFn = ()=>{
    console.log("子组件");
    return (
        <div>
            <h5>子组件(函数式组件)</h5>
        </div>
    )
}
export default memo(SonFn);
3)、解决方案:把传给子组件的函数属性,用useCallback包裹。

格式:let 新的函数 = useCallback(曾经的函数, [依赖的值])

如下是修改后的代码(只需要修改父组件的代码):

以下代码把increment函数进行了包裹

export default () => {
    console.log("父组件");
    const [count, setCount] = useState(1);

    let changeCount = () => {
        setCount(count + 1);
    }

    let increment = useCallback(()=>{
        console.log("increment");
    },[]) // 该函数永远不会重新定义(没有依赖)
    
    /*
    let increment = useCallback(()=>{
        console.log("increment");
    },[count]) // 当count的值发生变化是,该函数才会重新定义
    */
    return (
        <>
            <h1>useCallback</h1>
            <p>{count}</p>
            <input type="button" value="修改count" onClick={changeCount} />
            <hr/>
            <SonFn onMyClick={increment} />
        </>
    )
}
三、总结:

1、“万恶之源” :函数式组件每次重新渲染时,都会把函数体里的所有代码执行一遍。

2、useCallback解决的是 防止函数式组件里的 子函数(闭包) 多次被定义。既就是:useCallback是保证函数式组件重新渲染时,组件里的函数(闭包)只被定义一次!