阅读本文章需要对 React hooks 中 useState 和 useEffect 有基础的了解。我的这篇文章内有大致介绍 在 React 项目中全量使用 Hooks。
官方文档:
Pass an inline callback and an array of dependencies. useCallback will return a memoized version of the callback that only changes if one of the dependencies has changed.
简单来说就是返回一个函数,只有在依赖项发生变化的时候才会更新(返回一个新的函数)。
在线代码: Code Sandbox
import React, { useState, useCallback } from 'react'; import Button from './Button'; export default function App() { const [count1, setCount1] = useState(0); const [count2, setCount2] = useState(0); const handleClickButton1 = () => { setCount1(count1 + 1); }; const handleClickButton2 = useCallback(() => { setCount2(count2 + 1); }, [count2]); return ( <div> <div> <Button onClickButton={handleClickButton1}>Button1</Button> </div> <div> <Button onClickButton={handleClickButton2}>Button2</Button> </div> </div> ); } 复制代码
// Button.jsx import React from 'react'; const Button = ({ onClickButton, children }) => { return ( <> <button onClick={onClickButton}>{children}</button> <span>{Math.random()}</span> </> ); }; export default React.memo(Button); 复制代码
在案例中可以点击 Button1 和 Button2 两个按钮来查看效果,点击 Button1 的时候只会更新 Button1 后面的内容,点击 Button2 会将两个按钮后的内容都更新。这就表示我在点击 Button2 的时候导致了两个按钮内都重新渲染了。
这里或许会注意到
React.memo
这个方法,此方法内会对 props 做一个浅层比较,如果如果 props 没有发生改变,则不会重新渲染此组件。
const a = () => {}; const b = () => {}; a === b; // false 复制代码
上述代码可以看到我们两个一样的函数却是不相等的(这是个废话,我相信能看到这的人都知道,所以不做解释了)。
const [count1, setCount1] = useState(0); // ... const handleClickButton1 = () => { setCount1(count1 + 1); }; // ... return <Button onClickButton={handleClickButton1}>Button1</Button> 复制代码
回头再看上面的 Button
组件都需要一个 onClickButton 的 props ,尽管组件内部有用 React.memo
来做优化,但是我们声明的 handleClickButton1
是直接定义了一个方法,这也就导致只要是父组件重新渲染(状态或者props更新)就会导致这里声明出一个新的方法,新的方法和旧的方法尽管长的一样,但是依旧是两个不同的对象,React.memo
对比后发现对象 props 改变,就重新渲染了。
const handleClickButton2 = useCallback(() => { setCount2(count2 + 1); }, [count2]); 复制代码
上述代码我们的方法使用 useCallback 包装了一层,并且后面还传入了一个 [count2]
变量,这里 useCallback 就会根据 count2
是否发生变化,从而决定是否返回一个新的函数,函数内部作用域也随之更新。
由于我们的这个方法只依赖了 count2
这个变量,而且 count2
只在点击 Button2 后才会更新 handleClickButton2
,所以就导致了我们点击 Button1 不重新渲染 Button2 的内容。
import React, { useState, useCallback } from 'react'; import Button from './Button'; export default function App() { const [count2, setCount2] = useState(0); const handleClickButton2 = useCallback(() => { setCount2(count2 + 1); }, []); return ( <Button count={count2} onClickButton={handleClickButton2} >Button2</Button> ); } 复制代码
我们调整了一下代码,将 useCallback 依赖的第二个参数变成了一个空的数组,这也就意味着这个方法没有依赖值,将不会被更新。且由于 JS 的静态作用域导致此函数内 count2
永远都 0
。
可以点击多次 Button2 查看变化,会发现 Button2 后面的值只会改变一次。因为上述函数内的 count2
永远都是 0
,就意味着每次都是 0 + 1
,Button 所接受的 count
props,也只会从 0
变成 1
且一直都将是 1
,而且 handleClickButton2
也因没有依赖项不会返回新的方法,就导致 Button 只会因 count
改变而更新一次后就不会被重新渲染。
官方文档:
Pass a “create” function and an array of dependencies. useMemo will only recompute the memoized value when one of the dependencies has changed.
简单来说就是传递一个创建函数和依赖项,创建函数会需要返回一个值,只有在依赖项发生改变的时候,才会重新调用此函数,返回一个新的依赖值。
useMemo 与 useCallback 很像,根据上述 useCallback 已经可以想到 useMemo 也能针对传入子组件的值进行缓存优化,当然这个值必须是一个对象,如果不是对象而是一些简单类型的如字符串等,那么没更改 React.memo
也能对比出来,下面就直接举个 🌰 对比一下。
// ... const [count, setCount] = useState(0); const userInfo = { // ... age: count, name: 'Jace' } return <UserCard userInfo={userInfo}> 复制代码
// ... const [count, setCount] = useState(0); const userInfo = useMemo(() => { return { // ... name: "Jace", age: count }; }, [count]); return <UserCard userInfo={userInfo}> 复制代码
很明显的上面的 userInfo 每次都将是一个新的对象,无论 count
发生改变没,都会导致 UserCard 重新渲染,而下面的则会在 count
改变后才会返回新的对象。
实际上 useMemo 的作用不止于此,根据官方文档内介绍,它主要的功能应该是:
This optimization helps to avoid expensive calculations on every render.
可以吧一些昂贵的计算逻辑放到 useMemo 中,只有当依赖值发生改变的时候才去更新。
const num = useMemo(() => { let num = 0; // 这里使用 count 针对 num 做一些很复杂的计算,当 count 没改变的时候,组件重新渲染就会直接返回之前缓存的值。 return num; }, [count]); return <div>{num}</div> 复制代码
也能在很多情况将两种情况结合起来用。
结束。