React hooks 之 React.memo useMemo useCallback

先说一句,react hooks里,大多情况下比较都是浅比较,比如useEffect的浅比较是使用Object.is(arg1, arg2)来比较两个值,想必其他钩子也是如此,这种情况下,如果是基本类型则不会有问题,如果是引用类型,则比较的是两个参数的地址,而非值,比如,Object.is({a: 1}, {a: 1})的结果为false,即使两个对象都是{a: 1},但是由于地址不同,结果就为false。我们知道引用类型每次声明地址都会发生改变,所以比较引用类型,必然会触发更新函数,不管引用类型里面的内容是否有改变。

React.memo

import React, { useState } from 'react'
import NewMemo from './NewMemo'

const MemoParent:React.FC = () => {
    const [count, setCount] = useState(0)
    console.log('parent re render')
    return (
        <>
            <button onClick={() => { setCount(count + 1) }}>{count}</button>
            <NewMemo />
        </>
    )
}

export default MemoParent;
import React, { useEffect } from 'react'

interface NewMemoProps {
    data?: any
}


const NewMemo:React.FC<NewMemoProps> = () => {
    console.log('re render')
    return (
        <>
            <span>memo测试 哦哦哦</span>
        </>
    )
}

export default NewMemo;

先看上面两段代码,MemoParent中引入了NewMemo组件,当点击button时,修改内部的一个state, 此时看一下控制台的输出
在这里插入图片描述
由于函数组件是无状态的,所以每次state发生改变,函数都会重新执行一次,故而parent re render 执行了三次, 但是,子组件并没有依赖于父组件的任何状态,却也re render了三次,这肯定造成了一定程度的性能浪费,假如子组件中有较大的计算,这时候性能耗费就比较大了。
这时候就可以使用React.Memo来解决这个问题。
用法也相当简单,React.memo是个高阶函数,即传入一个组件,返回一个新的组件,故而只要将子组件的导出方式修改一下即可,如下

export default React.memo(NewMemo);

此时再看一下控制台的输出
在这里插入图片描述
可以看到子组件除了一开始执行了一次re render,后续父组件改变state并不会让子组件re render。
另外,memo可以传入第二个参数,第二个参数是一个函数,用于自定义比较函数,函数中可以接收prevProp和 curProp, 并且返回boolean 作为是否需要更新的依据

useCallback

然而当我们给子组件传入参数时,假如参数是个固定值,则不会有影响,但是如果参数发生了变化,比如,传入了一个点击事件onClick,那么每次父组件render的时,都会重新声明一个onClick,即便内容没有变化,子组件也会认为onClick发生了改变进而重新渲染自身,因为函数是引用类型,react中默认对比都是用浅比较,对于引用类型,浅比较只是比较了两个引用类型的地址而非值,useEffect中的比较亦是如此,代码如下。

const MemoParent:React.FC = () => {
    const [count, setCount] = useState(0)
    console.log('parent re render')
    const handleClick = () => {
        console.log('父组件给子组件的事件')
    }
    return (
        <>
            <button onClick={() => { setCount(count + 1) }}>{count}</button>
            <NewMemo handleClick={handleClick}></NewMemo>
        </>
    )
}

export default MemoParent;

此时就可以通过useCallback,给父组件的函数做缓存,当依赖发生变化时,才认为该函数发生了改变,否则不会重新声明该函数。

const MemoParent:React.FC = () => {
    const [count, setCount] = useState(0)
    console.log('parent re render')
    const handleClick = useCallback(() => {
        console.log('父组件给子组件的事件')
    }, [count])
    return (
        <>
            <button onClick={() => { setCount(count + 1) }}>{count}</button>
            <NewMemo handleClick={handleClick}></NewMemo>
        </>
    )
}

export default MemoParent;

这里依赖为count,则只有当count发生变化时,才会重新声明handleClick,子组件重新渲染,否则不会重新声明handleClick,子组件也不会重新渲染,另外如果依赖是个空数组,则改方法就只会声明一次,之后都不会发生改变。

需要注意这里useCallback的依赖,如果是个引用类型。。则依然会出现上面说过的问题,故而依赖尽量不要用引用类型,否则使用useCallback的意义就不大了,或者可以是引用类型中的某个值,这个值是基本类型

useMemo

useMemo的用法和useCallback很像,区别是useCallback缓存的是方法,useMemo缓存的是值,并且两者一定程度上可以相互转换

const MemoParent:React.FC = () => {
    const [count, setCount] = useState(0)
    console.log('parent re render')
    const userInfo = {
        age: 14,
        name: 'xxx'
    }
    return (
        <>
            <button onClick={() => { setCount(count + 1) }}>{count}</button>
            <NewMemo userInfo={userInfo}></NewMemo>
        </>
    )
}

这段代码中,MemoParent向子组件NewMemo传入了userInfo这个对象,每次点击button时,父组件re render,子组件也会re render, 但是userInfo明明没有发生变化,这肯定不是我们想要的。
那么我们就可以用useMemo将userInfo的结果缓存下来,并且可以设置依赖,当依赖发生变化时,才认为userInfo发生了变化,否则re render时不会重新声明userInfo

const MemoParent:React.FC = () => {
    const [count, setCount] = useState(0)
    console.log('parent re render')
    const userInfo = useMemo(() => {
        return {
            age: 14,
            name: 'xxx'
        }
    }, [])
    return (
        <>
            <button onClick={() => { setCount(count + 1) }}>{count}</button>
            <NewMemo userInfo={userInfo}></NewMemo>
        </>
    )
}

如上,这里依赖为空,也就意味着userInfo只会被初始化一次,之后点击button,子组件都不会被重新渲染,如果依赖里有变量,则当该变量发生变化时,才会重新声明userInfo,如果变量是个引用类型,请参考上面的回答。

这里仅仅说了三者最基本的使用,react对我来说尚处于一个比较陌生的阶段,若有写的不好的地方望指出

简单来说,当父组件没有给子组件传参,或者传入的参数是基本类型的时候,那么这时给子组件套一层memo就可以防止子组件重复render, 当传入的参数是引用类型时,这时候就需要用到useCallback 或者 useMemo

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值