React---关于useCallback和useMemo的详解

1. 什么是useCallback和useMemo?

         useCallback 和 useMemo 都是react可用于性能优化的内置hooks。

        两者的区别在于:useCallback缓存的是一个函数,而useMemo缓存的是计算结果。

        其使用语法如下:

// useCallback
// 第一个参数是一个回调函数,useCallback会缓存这个函数,返回缓存的回调函数
// 第二个参数是依赖项,只有当依赖项改变时,才会重新创建这个函数
const memorizedCallback = useCallback(()=>{
    doSomething(a,b);
},[a,b])
 
// useMemo
// 第一个参数是一个函数,useMemo会缓存函数运行返回的值,返回缓存的值
// 第二个参数是依赖项,只有当依赖改变时,才会重新计算这个值
const memorizedValue = useMemo(()=>computeValue(a,b),[a,b])

2. 为什么使用useCallback和useMemo?

         在函数式组件中,每次UI的变化,都是通过重新执行整个函数来完成的,这和传统的类组件有很大区别:函数组件中并没有一个直接的方式在多次渲染之间维持一个状态。

        在重新执行整个函数组件的过程中,其中的函数和引用类型的变量会创建新的(指向新的引用),导致函数组件在re-render前后,其中函数和引用类型变量是不相等的,这又会导致其他非必要的re-render。比如以下例子:

function Counter() {
  const [count, setCount] = useState(0);
  // 只要组件状态发生变化,每次都要创建一个新的事件处理函数
  const handleIncrement = () => setCount(count + 1);
  // ...
  // 每次创建新函数的方式会让接收事件处理函数的组件需要重新渲染
  return <Button handleClick={handleIncrement}/>
}

        当Counter组件因为其他数据(非count)发生变化而导致重新渲染的时候,重新执行整个Counter函数,会创建新的handleIncrement函数,而子组件Button会由于props-handleClick传入的handleIncrement函数改变而重新渲染,但其实这个渲染是不必要的,因为只有在count发生变化时,才应该导致Button组件的渲染。

        此时,因为需要缓存的事handleIncrement函数,所以用useCallback优化一下。

// 需要做到:只有当count发生变化时,才需要重新定一个回调函数-useCallback
function Counter() {
  const [count, setCount] = useState(0);
  const handleIncrement = useCallback(
      () => setCount(count + 1),
      [count]  
  )  
// 只有当依赖项count改变时,才会重新生成函数,不然都是返回的缓存的回调函数,不会触发Button子组件的重绘
  // ...
  return <Button handleClick={handleIncrement}/>
}

3. 什么时候使用useCallback和useMemo?   

         3.1 useCallback
        当子组件接收一个函数props时,一般会使用useCallback来缓存这个函数,减少不必要的re-render。以下例子:向子组件传递一个函数,在父组件每次re-render的时候,函数会重新创建新的,这会导致使用这个函数props的子组件也re-render,但这是不必要的,可以用useCallback来解决。

function ParentComponent() {
    const onHandleClick = useCallback(() => {
        // this will return the same function
        // instance between re-renders
    });
    return (
        <MemoizedSubComponent
        handleClick={onHandleClick}
        />
      );
}

          3.2 useMemo
        useMemo常用在以下两种场景的优化中:1)引用类型的变量   2)需要大量时间执行的计算函数。

const UseMemoDemo = () => {
    // 调用这个函数需要大量时间去计算
    const slowFunction = (number) => {
        console.log('calling slow function')
        for (let i = 0; i <= 1000000000; i++) {
        }
        return number * 2
    }
    const [inputNumber, setInputNumber] = useState(1)
    const [dark, setDark] = useState(true)
 
    // 场景1:执行某函数需要大量时间,使用useMemo来优化,在不必要执行函数的时候不执行函数
    const doubleNumber = useMemo(() => {
        return slowFunction(inputNumber)
    }, [inputNumber])
 
    // 场景2:每次组件更新会重新执行,内部的引用类型变量会重新创建,这会导致使用到引用类型变量的组件重新渲染,使用useMemo来让每次的变量相同
    const themeStyle = useMemo(() => {
        return {
            background: dark ? 'black' : 'white',
            color: dark ? 'white' : 'black'
        }
    }, [dark])
 
    useEffect(() => {
        console.log('themeStyle changed')
    }, [themeStyle])
    const handleChange = (e) => {
        setInputNumber(parseInt(e.target.value))
    }
    return (
        <>
            <input type='text' value={inputNumber} onChange={handleChange}/>
            <button onClick={() => {
                setDark(prevDark => !prevDark)
            }}>change theme
            </button>
            <p style={themeStyle}>{doubleNumber}</p>
        </>
    )
}
export default UseMemoDemo;

        在使用useMemo优化slowFunction这个耗时较长的函数之前,不管是改变input的值,使下方显示input值*2(这是实际需要运行slowFunction这个耗时函数的);还是点击button切换主题(这时不需要运行slowFunction这个耗时函数);都犹豫需要组件重新渲染,导致运行slowFunction这个函数,造成性能问题。点击切换主题时,也有明显的延迟。

        在优化后,改变主题不再运行slowFunction函数,因为useMemo缓存了计算结果,只要inputNumber这个依赖项没有改变,就不会重新计算。优化后,点击切换主题不再有明显的延迟。

        对于themeStyle引用类型变量的优化,也是相同的道理。当因为改变input值导致组件重新渲染时,实际上themeStyle变量是没有改变的,但由于要重新执行组件函数,所以创建了新的引用。这使得使用到引用类型变量的组件(button)重新渲染,造成性能浪费。

        在使用useMemo优化后,themeStyle只会在变量dark的改变时改变,其他时候是useMemo缓存的值,不会因为其他无关变量改变或组件重绘而改变,造成不必要的组件re-render。

最后,更多的关于什么时候使用useCallback和useMemo需要在项目实践中花时间去思考性能优化的点。不能盲目地使用useCallback和useMemo,因为两者都需要内存去缓存,过多的非必要的使用也是不利于应用的性能的。

  • 5
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值