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

本文深入探讨了React中React.memo、useCallback和useMemo这三个优化性能的 Hooks 的应用场景。通过实例展示了如何避免不必要的组件渲染,通过使用memo包裹组件、useCallback缓存函数以及useMemo优化复杂数据传递,从而提高应用性能。
摘要由CSDN通过智能技术生成

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

React.memo()

问题

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

子组件:

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

父组件:

function ParentComp () {
  const [ count, setCount ] = useState(0)
  const increment = () => setCount(count + 1)

  return (
    <div>
      <button onClick={increment}>点击次数:{count}</button>
      <ChildComp />
    </div>
  );
}

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

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

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

解决

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

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

import React, { memo } from 'react'

const ChildComp = memo(function () {
  console.log('render child-comp ...')
  return <div>Child Comp ...</div>
})

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

import React, { memo } from 'react'

let ChildComp = function () {
  console.log('render child-comp ...')
  return <div>Child Comp ...</div>
}

ChildComp = memo(ChildComp)

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

React.useCallback()

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

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

import React, { memo } from 'react'

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

父组件:

function ParentComp () {
  const [ count, setCount ] = useState(0)
  const increment = () => setCount(count + 1)

  const [ name, setName ] = useState('hi~')
  const changeName = (newName) => setName(newName)  // 父组件渲染时会创建一个新的函数

  return (
    <div>
      <button onClick={increment}>点击次数:{count}</button>
      <ChildComp name={name} onClick={changeName}/>
    </div>
  );
}

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

分析下原因:

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

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

解决

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

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

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

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

React.useMemo()

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

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

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

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

解决

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

useMemo 有两个参数:

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

子组件:

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

父组件:

function DifMemoCallBack() {
    const [count, setCount] = useState(0);
    const increment = ()=> setCount(count+1);

    const [name, setName] = useState('hi~');
    const [age, setAge] = useState(20);
    
    const changeName = useCallback((newName) => setName(newName),[]);

    //当父组件向子组件传的是复杂数据类型时,子组件也会重新渲染
    //父组件渲染,const info = { name, age } 一行会重新生成一个新对象,导致传递给子组件的info属性值变化,进而导致子组件重新渲染
    // const info = {name,age};  //复杂数据类型
    const info = useMemo(() => ({name,age}),[name,age]);

    return (
        <div>
            <button onClick={increment}>点击次数:{count}</button>
            <ChildComp name={info} onClick={changeName}/>
        </div>
    )
}
export default DifMemoCallBack;

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

总结:

function DifMemoCallBack() {
    const [count, setCount] = useState(0);
    const increment = ()=> setCount(count+1);

    const [name, setName] = useState('hi~');
    const [age, setAge] = useState(20);

    //当父组件只调用了子组件,没有传值时,在子组件中使用memo可解决重新渲染的问题
    // const changeName = (newName) => setName(newName); //父组件渲染时会创建一个新的函数

    //当父组件向子组件中传值,且传的是字符串时,传过去的changeName重新创建了,导致子组件渲染,使用useCallback完善这一缺陷
    const changeName = useCallback((newName) => setName(newName),[]);

    //当父组件向子组件传的是复杂数据类型时,子组件也会重新渲染
    //父组件渲染,const info = { name, age } 一行会重新生成一个新对象,导致传递给子组件的info属性值变化,进而导致子组件重新渲染
    // const info = {name,age};  //复杂数据类型
    const info = useMemo(() => ({name,age}),[name,age]);

    return (
        <div>
            <button onClick={increment}>点击次数:{count}</button>
            {/*<ChildComp name={name} onClick={changeName}/>*/}

            <ChildComp name={info} onClick={changeName}/>
        </div>
    )
}
export default DifMemoCallBack;

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
当你在使用React时,你可能会经常遇到需要优化组件性能的情况。React.memouseMemo都是React提供的两种优化性能的方式。 React.memo是一个高阶组件(Higher Order Component),它可以优化组件的性能,使得组件只有在它的props发生改变时才会重新渲染。这种优化是通过比较前一次和当前props的浅层比较来实现的。使用React.memo时,需要将组件作为React.memo函数的参数传递,例如: ``` import React from 'react'; const MyComponent = React.memo(props => { // 组件代码 }); export default MyComponent; ``` useMemo是一个React的hook函数,它可以缓存组件的一些计算结果,以避免重复计算。useMemo接收两个参数:一个计算函数和一个依赖数组。当依赖数组的任何一个值发生改变时,useMemo会重新计算并返回新的值。如果依赖数组的任何一个值都没有发生改变,则会返回上一次缓存的值。使用useMemo时,需要将计算函数作为useMemo的第一个参数传递,依赖数组作为第二个参数传递,例如: ``` import React, { useMemo } from 'react'; const MyComponent = props => { const expensiveCalculation = useMemo(() => { // 计算代码 }, [props.dependency]); // 组件代码 }; export default MyComponent; ``` 需要注意的是,React.memouseMemo都只是对组件性能进行优化的工具,并不是适用于所有情况的万能解决方案。在使用它们时,需要谨慎考虑依赖项和是否真的需要进行性能优化。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值