React.memo、useMemo 和 useCallback 简介

2025-05-24

React.memo、useMemo 和 useCallback 简介

先决条件:关于React 的基础知识

封面照片由 Danika Perkinson 在 Unsplash 上拍摄

当我开始写这篇博客时,我问自己是否应该只讨论useMemouseCallback之间的区别,因为它们是 React Hooks,而React.memo不是。最终,我决定也包含React.memo,因为一方面,这memo两个术语中的“memo”听起来可能有点让人困惑。另一方面,这都与 React 优化有关 😁

优化

1. React.memo 是什么

如果你熟悉React.PureComponent,那么React.memo就相当简单了,因为它和React.PureComponent完全类似。我们将React.PureComponent与类组件一起使用,而React.memo则与函数式组件一起使用 👌

让我们看一下示例,看看它是如何工作的。Codesandbox

注意:以下所有示例仅用于表达主要思想。实际上,在这种简单的情况下,我们不需要优化

const App = () => {
    const [count1, setCount1] = React.useState(0)
    const [count2, setCount2] = React.useState(0)

    const increaseCounter1 = () => {
        setCount1(count1 => count1 + 1)
    }

    return (
        <>
            <button onClick={increaseCounter1}>Increase counter 1</button>
            <Counter value={count1}>Counter 1</Counter>
            <Counter value={count2}>Coutner 2</Counter>
        </>
    )
}
Enter fullscreen mode Exit fullscreen mode
const Counter = ({value, children}) => {
    console.log('Render: ', children)

    return (
        <div>
            {children}: {value}
        </div>
    )
}

export default Counter
Enter fullscreen mode Exit fullscreen mode

每次用户点击按钮时,状态都会发生count1变化,导致应用重新渲染两个计数器,这被称为不必要的重新渲染。然而,我们预计只有counter1会重新渲染,因为counter2没有任何变化。实际上,两个计数器都会重新渲染。

我们该如何解决这个问题呢?🤔 答案是:React.memo。我们需要做的就是将 Counter 组件包裹在React.memo中。

const Counter = ({value, children}) => {
    console.log('Render: ', children)

    return (
        <div>
            {children}: {value}
        </div>
    )
}

export default React.memo(Counter)
Enter fullscreen mode Exit fullscreen mode

默认情况下,React.memo会通过引用相等性比较传递给组件的所有 props 。如果这些 props 未更改,React.memo将重用上次渲染的结果,从而防止组件重新渲染。在我们的示例中,React.memovalue会检查自上次渲染以来,和props是否有任何变化。由于我们的按钮仅更改了counter1children的值,因此React.memo将防止counter2重新渲染。😎💪

我们还可以通过提供自定义比较函数作为第二个参数来覆盖React.memo的默认比较。

来自 React.memo文档

const Counter = () => {

   const areEqual = (prevProps, nextProps) => {
     /*
     return true if passing nextProps to render would return
     the same result as passing prevProps to render,
     otherwise return false
     */
   } 
}

export default React.memo(Counter, areEqual)
Enter fullscreen mode Exit fullscreen mode

2. useMemo 和 useCallback

我将从文档开始

useMemo 返回记忆值

React.useMemo(() => {
  fooFunction()
}, [dependencies])
Enter fullscreen mode Exit fullscreen mode

useCallback 返回一个记忆回调

React.useCallback(() => {
  fooFunction()
}, [dependencies])
Enter fullscreen mode Exit fullscreen mode

💪 让我们一起分解

React.useMemoReact.useCallback接收一个函数作为其第一个参数,一个依赖项数组作为其第二个参数。只有当其中一个依赖项的值发生变化(引用相等)时,钩子才会返回一个新值。主要区别在于React.useMemo会调用fooFunction并返回其结果,而React.useCallback则会返回fooFunction而不调用它。

😫 例如请codesandbox

const App = () => {
    const fooFunction = () => {
        return 'Foo is just Food without D'
    }

    const useMemoResult = React.useMemo(fooFunction, [])
    const useCallbackResult = React.useCallback(fooFunction, [])

    console.log('useMemoResult: ', useMemoResult)
    console.log('useCallbackResult: ', useCallbackResult)

    return <p>Foo is just food without D</p>
}
Enter fullscreen mode Exit fullscreen mode

如果你运行代码并查看你的控制台,而不是冰箱,你会看到以下输出

使用备忘录和回调结果

React.useMemo运行 fooFunction 并返回一个字符串Foo is just Food without D,而React.useCallback只返回一个 fooFunction 而不调用它

🤩 明白了。但它在 React 中如何工作?

🍀 useMemo

通常,当我们计算昂贵的值时,我们可以使用 React.useMemo ,因为我们不想在组件重新渲染时一遍又一遍地计算它

const Me = ({girlFriendWords}) => {

    // Provided that girlFriendWords is a string

    const myReply = decideWhatToSay (girlFriendWords)

    return <p>{myReply}</p>
}
Enter fullscreen mode Exit fullscreen mode

想象一下,计算myReply价值需要耗费我全部的精力,如果当我的女朋友说了些什么(重新渲染)时我不得不一遍又一遍地这样做(重新计算),那该怎么办?🤐

🔥 React.useMemo来拯救你

const Me = ({girlFriendWords}) => {

    // Provided that girlFriendWords is a string

    const myReply = React.useMemo(() => decideWhatToSay (girlFriendWords), [girlFriendWords])

    return <p>{myReply}</p>
}
Enter fullscreen mode Exit fullscreen mode

感谢React.useMemo,没有你我不可能完成这个博客💑

React.useMemo依赖[girlFriendWords]数组,这意味着它只会在值发生变化decideWhatToSay时运行函数girlFriendWords。当我女朋友说同样的话时,我不用再三考虑就能回复。优化就在这里🎉🍀💐

女友优化

🍀 useCallback

关系说得够多了,让我们回到 Counter 的例子。稍微调整一下例子,现在我们的 counter 也接收onClick一个 function 作为 prop。你能猜一下,当值改变时,我们的Counter2组件是否会重新渲染吗?count1

const App = () => {
    const [count1, setCount1] = React.useState(0)
    const [count2, setCount2] = React.useState(0)

    const increaseCounter1 = () => {
        setCount1(count1 => count1 + 1)
    }

    const increaseCounter2 = () => {
            setCount1(count2 => count1 + 1)
    }

    return (
        <>
            <Counter value={count1} onClick={increaseCounter1}>Counter 1</Counter>
            <Counter value={count2} onClick={increaseCounter2}>Coutner 2</Counter>
        </>
    )
}
Enter fullscreen mode Exit fullscreen mode
const Counter = ({value, children, onClick}) => {
    console.log('Render: ', children)

    return (
        <Button onClick={}>
            {children}: {value}
        </div>
    )
}

export default React.memo(Counter)
Enter fullscreen mode Exit fullscreen mode

答案是肯定的😮。

即使我们使用 React.memo,当只有 发生变化时, counter2组件仍然会重新渲染,count1因为React.memo使用引用相等性来避免不必要的渲染。然而,当 App 重新渲染时,increaseCounter2会被重新创建,因此onClick每次传递给 Counter 组件的 props 都是不同的,这会导致组件重新渲染。避免此问题的简单方法是防止increaseCounter2App 重新渲染时重新创建该函数。

我们利用React.useCallback来做到这一点

const App = () => {
    const [count1, setCount1] = React.useState(0)
    const [count2, setCount2] = React.useState(0)

    const increaseCounter1 = React.useCallback(() => {
        setCount1(count1 => count1 + 1)
    }, [])

    const increaseCounter2 = React.useCallback(() => {
            setCount2(count2 => count1 + 1)
    }, [])

    return (
        <>
            <Counter value={count1} onClick={increaseCounter1}>Counter 1</Counter>
            <Counter value={count2} onClick={increaseCounter2}>Coutner 2</Counter>
        </>
    )
}
Enter fullscreen mode Exit fullscreen mode

看一下依赖项数组,它是空的,因为我只想创建这些函数一次。这样,onClick传递给 Counter 组件的 props 就始终相同了。

3.结论:

  • 🚀 我们不应该在不先衡量成本的情况下优化不必要的重新渲染。优化总是有代价的。
  • 🚀 React.memo与React.PureComponent类似,不同之处在于它用于功能组件,而React.PureComponent仅用于类组件
  • 🚀 React.useMemo返回一个记忆值,而React.useCallback返回一个记忆回调

这里有一些适合您的优质资源:

🙏💪 感谢阅读!

我很乐意听听你的想法和反馈。欢迎在下方留言!

✍️ 作者

惠贞 🔥 🎩 ♥️ ♠️ ️ ♣️ 🤓

软件开发人员 | 魔术爱好者

打招呼👋

Github

LinkedIn

中等

文章来源:https://dev.to/dinhhuyams/introduction-to-react-memo-usememo-and-usecallback-5ei3
PREV
SVG 变得简单
NEXT
React JS 2024 路线图