• React中memo()、useCallback()、useMemo() 区别使用详解


    本文转载自memo()、useCallback()、useMemo() 区别使用详解,文中讲解useMemo时,代码示例不详细,在此做个笔记,并补充详细。

    React.memo()

    问题

    React 中当组件的 props 或 state 变化时,会重新渲染视图,实际开发会遇到不必要的渲染场景。看个例子

    子组件:

    1. function ChildComp () {
    2. console.log('render child-comp ...')
    3. return <div>Child Comp ...</div>
    4. }

    父组件:

    1. function ParentComp () {
    2. const [ count, setCount ] = useState(0)
    3. const increment = () => setCount(count + 1)
    4. return (
    5. <div>
    6. <button onClick={increment}>点击次数:{count}</button>
    7. <ChildComp />
    8. </div>
    9. );
    10. }

    子组件中有条 console 语句,每当子组件被渲染时,都会在控制台看到一条打印信息。

    点击父组件中按钮,会修改 count 变量的值,进而导致父组件重新渲染,此时子组件压根没有任何变化(props、state),但在控制台中仍然看到子组件被渲染的打印信息。

    我们期待的结果:子组件的 props 和 state 没有变化时,即便父组件渲染,也不要渲染子组件。

    解决

    1、修改子组件,用 React.memo() 包一层。

    这种写法是 React 的高阶组件写法,将组件作为函数(memo)的参数,函数的返回值(ChildComp)是一个新的组件。

    1. import React, { memo } from 'react'
    2. const ChildComp = memo(function () {
    3. console.log('render child-comp ...')
    4. return <div>Child Comp ...</div>
    5. })

    觉得上面👆那种写法别扭的,可以拆开写。

    1. import React, { memo } from 'react'
    2. let ChildComp = function () {
    3. console.log('render child-comp ...')
    4. return <div>Child Comp ...</div>
    5. }
    6. ChildComp = memo(ChildComp)

    此时再次点击按钮,可以看到控制台没有打印子组件被渲染的信息了。

    React.useCallback()

    上面的例子中,父组件只是简单调用子组件,并未给子组件传递任何属性
    看一个父组件给子组件传递属性的例子:

    子组件:(子组件仍然用 React.memo() 包裹一层)

    1. import React, { memo } from 'react'
    2. const ChildComp = memo(function ({ name, onClick }) {
    3. console.log('render child-comp ...')
    4. return <>
    5. <div>Child Comp ... {name}</div>
    6. <button onClick={() => onClick('hello')}>改变 name 值</button>
    7. </>
    8. })

    父组件:

    1. function ParentComp () {
    2. const [ count, setCount ] = useState(0)
    3. const increment = () => setCount(count + 1)
    4. const [ name, setName ] = useState('hi~')
    5. const changeName = (newName) => setName(newName) // 父组件渲染时会创建一个新的函数
    6. return (
    7. <div>
    8. <button onClick={increment}>点击次数:{count}</button>
    9. <ChildComp name={name} onClick={changeName}/>
    10. </div>
    11. );
    12. }

            父组件在调用子组件时传递了 name 属性和 onClick 属性,此时点击父组件的按钮,可以看到控制台中打印出子组件被渲染的信息。

    分析下原因:

            点击父组件按钮,改变了父组件中 count 变量值(父组件的 state 值),进而导致父组件重新渲染;父组件重新渲染时,会重新创建 changeName 函数,即传给子组件的 onClick 属性发生了变化,导致子组件渲染;由于子组件的 props 改变了,所以子组件渲染了,但是我们只是点击了父组件的按钮,并未对子组件做任何操作,压根就不希望子组件的 props 有变化。

    useCallback 钩子进一步完善这个缺陷。

    解决

    修改父组件的 changeName 方法,用 useCallback 钩子函数包裹一层。

    1. // 每次父组件渲染,返回的是同一个函数引用
    2. const changeName = useCallback((newName) => setName(newName), [])

    此时点击父组件按钮,控制台不会打印子组件被渲染的信息了。

    究其原因:useCallback() 起到了缓存的作用,即便父组件渲染了,useCallback() 包裹的函数也不会重新生成,会返回上一次的函数引用。

    React.useMemo()

    前面父组件调用子组件时传递的 name 属性是个字符串,如果换成传递对象会怎样?

    下面例子中,父组件在调用子组件时传递 info 属性,info 的值是个对象,点击父组件按钮时,发现控制台打印出子组件被渲染的信息。

    分析原因跟调用函数是一样的:

            点击父组件按钮,触发父组件重新渲染;父组件渲染,const info = { name, age } 一行会重新生成一个新对象,导致传递给子组件的 info 属性值变化,进而导致子组件重新渲染。

    解决

    使用 useMemo 对对象属性包一层。

    useMemo 有两个参数:

    第一个参数是个函数,返回的对象指向同一个引用,不会创建新对象;
    第二个参数是个数组,只有数组中的变量改变时,第一个参数的函数才会返回一个新的对象

    子组件:

    1. const ChildComp = memo(function ({name,onClick}) {
    2. console.log('render child-comp...');
    3. return <>
    4. <div>Child Comp ...{name.name}</div>
    5. <button onClick={()=>onClick('hello')}>改变name值</button>
    6. </>
    7. })

    父组件:

    1. function DifMemoCallBack() {
    2. const [count, setCount] = useState(0);
    3. const increment = ()=> setCount(count+1);
    4. const [name, setName] = useState('hi~');
    5. const [age, setAge] = useState(20);
    6. const changeName = useCallback((newName) => setName(newName),[]);
    7. //当父组件向子组件传的是复杂数据类型时,子组件也会重新渲染
    8. //父组件渲染,const info = { name, age } 一行会重新生成一个新对象,导致传递给子组件的info属性值变化,进而导致子组件重新渲染
    9. // const info = {name,age}; //复杂数据类型
    10. const info = useMemo(() => ({name,age}),[name,age]);
    11. return (
    12. <div>
    13. <button onClick={increment}>点击次数:{count}</button>
    14. <ChildComp name={info} onClick={changeName}/>
    15. </div>
    16. )
    17. }
    18. export default DifMemoCallBack;

    再次点击父组件按钮,控制台中不再打印子组件被渲染的信息了。

    总结:

    1. function DifMemoCallBack() {
    2. const [count, setCount] = useState(0);
    3. const increment = ()=> setCount(count+1);
    4. const [name, setName] = useState('hi~');
    5. const [age, setAge] = useState(20);
    6. //当父组件只调用了子组件,没有传值时,在子组件中使用memo可解决重新渲染的问题
    7. // const changeName = (newName) => setName(newName); //父组件渲染时会创建一个新的函数
    8. //当父组件向子组件中传值,且传的是字符串时,传过去的changeName重新创建了,导致子组件渲染,使用useCallback完善这一缺陷
    9. const changeName = useCallback((newName) => setName(newName),[]);
    10. //当父组件向子组件传的是复杂数据类型时,子组件也会重新渲染
    11. //父组件渲染,const info = { name, age } 一行会重新生成一个新对象,导致传递给子组件的info属性值变化,进而导致子组件重新渲染
    12. // const info = {name,age}; //复杂数据类型
    13. const info = useMemo(() => ({name,age}),[name,age]);
    14. return (
    15. <div>
    16. <button onClick={increment}>点击次数:{count}</button>
    17. {/*<ChildComp name={name} onClick={changeName}/>*/}
    18. <ChildComp name={info} onClick={changeName}/>
    19. </div>
    20. )
    21. }
    22. export default DifMemoCallBack;

  • 相关阅读:
    信而泰 X-Snapper测试系统,助力家庭路由器IPv6支持度测试
    供暖系统如何实现数据远程采集?贝锐蒲公英高效实现智慧运维
    OCR文字识别方法综述
    Redis各数据类型特定的命令和用法 1.0版本
    字符串相关算法
    在 Go 中管理多个数据库连接
    Oceanbase体验之(一)运维管理工具OCP部署(社区版4.2.2)
    《红警3》因计算机中丢失d3dx9_35.dll无法打开游戏怎么办?最新解决方法推荐
    ChatGPT可以取代搜索引擎吗?
    SAP 电商云 Spartacus UI Store 相关的设计明细
  • 原文地址:https://blog.csdn.net/liuye066/article/details/127809647