React.memo、useMemo 和 useCallback 简介
先决条件:关于React 的基础知识
封面照片由 Danika Perkinson 在 Unsplash 上拍摄
当我开始写这篇博客时,我问自己是否应该只讨论useMemo和useCallback之间的区别,因为它们是 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>
</>
)
}
const Counter = ({value, children}) => {
console.log('Render: ', children)
return (
<div>
{children}: {value}
</div>
)
}
export default Counter
每次用户点击按钮时,状态都会发生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)
默认情况下,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)
2. useMemo 和 useCallback
我将从文档开始
useMemo 返回记忆值
React.useMemo(() => {
fooFunction()
}, [dependencies])
useCallback 返回一个记忆回调
React.useCallback(() => {
fooFunction()
}, [dependencies])
💪 让我们一起分解
React.useMemo和React.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>
}
如果你运行代码并查看你的控制台,而不是冰箱,你会看到以下输出
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>
}
想象一下,计算myReply
价值需要耗费我全部的精力,如果当我的女朋友说了些什么(重新渲染)时我不得不一遍又一遍地这样做(重新计算),那该怎么办?🤐
🔥 React.useMemo来拯救你
const Me = ({girlFriendWords}) => {
// Provided that girlFriendWords is a string
const myReply = React.useMemo(() => decideWhatToSay (girlFriendWords), [girlFriendWords])
return <p>{myReply}</p>
}
感谢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>
</>
)
}
const Counter = ({value, children, onClick}) => {
console.log('Render: ', children)
return (
<Button onClick={}>
{children}: {value}
</div>
)
}
export default React.memo(Counter)
答案是肯定的😮。
即使我们使用 React.memo,当只有 发生变化时, counter2组件仍然会重新渲染,count1
因为React.memo使用引用相等性来避免不必要的渲染。然而,当 App 重新渲染时,increaseCounter2
会被重新创建,因此onClick
每次传递给 Counter 组件的 props 都是不同的,这会导致组件重新渲染。避免此问题的简单方法是防止increaseCounter2
App 重新渲染时重新创建该函数。
我们利用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>
</>
)
}
看一下依赖项数组,它是空的,因为我只想创建这些函数一次。这样,onClick
传递给 Counter 组件的 props 就始终相同了。
3.结论:
- 🚀 我们不应该在不先衡量成本的情况下优化不必要的重新渲染。优化总是有代价的。
- 🚀 React.memo与React.PureComponent类似,不同之处在于它用于功能组件,而React.PureComponent仅用于类组件
- 🚀 React.useMemo返回一个记忆值,而React.useCallback返回一个记忆回调
这里有一些适合您的优质资源:
🙏💪 感谢阅读!
我很乐意听听你的想法和反馈。欢迎在下方留言!
✍️ 作者
惠贞 🔥 🎩 ♥️ ♠️ ️ ♣️ 🤓
软件开发人员 | 魔术爱好者
打招呼👋
✅ Github
✅中等
文章来源:https://dev.to/dinhhuyams/introduction-to-react-memo-usememo-and-usecallback-5ei3