react hooks中, useEffect的依赖为引用类型如何处理?

什么是useEffect ?

react推出的,用于执行副作用的钩子

什么是副作用?

在react组件中, 执行数据请求 、 dom操作,统一称之为副作用

如何使用?

useEffect接收两个参数,第一个是执行函数,第二个是依赖数组

import React, { useEffect, useRef, useState } from 'react'

const ClickCount = () => {
    const [count, setCount] = useState(0)
    const preObj = useRef({a: 1})
    const [obj, setObj] = useState(preObj.current)
    useEffect(() => {
        console.log('excuted')
    })
    return (
        <div>
            {count}
            <button onClick={() => {setCount(count + 1)}}>click</button>
        </div>
    )
}

export default ClickCount

有如上这段代码, 当每次点击button时,都会执行setCount函数,count发生改变,由于此时useEffect没有传入第二个参数,所以excuted 会被打印。

如果给useEffect第二个参数传入一个空数组,则此时useEffect只会在组件初始化的时候执行一次,我认为接口调用放在这里执行比较合适。

若数组不为空,比如 [count], 则 useEffect 会在count发生改变的时候执行, 有点类似于 vue中的watch监听。

以上都没有太大问题,但是很多情况下,我们会需要在依赖中传入对象,那么此时就会发生问题。

useEffect需要将此次渲染时,依赖中的值与上次渲染依赖中的值做对比, 如果结果为true, 表示两者一致,未发生改变,故而不需要再次渲染, 若结果为false,表示发生了改变,会重新调用执行函数。并且,useEffect的比较为浅比较,查阅资料之后发现是调用Object.is(arg1, arg2),若参数为基本类型,则不会有问题,但是如果为引用类型, 则比较的是两者的地址,而非值, 即 : Object.is({a: 1}, {a: 1})的结果为false, 会再次调用执行函数,这就是依赖最好不要传入引用类型的原因,并且,如果在useEffect中修改了引用类型,则会引发无限渲染的问题

  • 那么如何解决依赖为引用类型的问题呢?
    • 比较简单的方法是使用 ‘use-deep-compare-effect’ 这个库,从名字就知道,他可以进行深比较,使用方法也很简单, import useDeepCompareEffect from ‘use-deep-compare-effect’ 之后用useDeepCompareEffect 替换掉 原有的 useEffect 即可。
    • 以下代码就不会有无限渲染问题,同时useDeepCompareEffect中的执行函数也只会在obj中某一个key的值发生变化的时候才会执行。
import React, { useEffect, useRef, useState } from 'react'
import useDeepCompareEffect from 'use-deep-compare-effect'

const ClickCount = () => {
    const [count, setCount] = useState(0)
    const [obj, setObj] = useState({a: 1})
    useDeepCompareEffect(() => {
        console.log('excuted')
    }, [obj])
    return (
        <div>
            {count}
            <button onClick={() => {setObj({a: 2})}}>click</button>
        </div>
    )
}

export default ClickCount

查阅资料之后也可以使用 useRef这个钩子来解决上述问题,useRef的特性是跨渲染周期保存数据
代码如下

import React, { useEffect, useRef, useState } from 'react'
import useDeepCompareEffect from 'use-deep-compare-effect'

function usePrevious(value: any) {
    const ref = useRef()

    useEffect(() => {
        ref.current = value
    }, [value])
    return ref.current
}

const ClickCount = () => {
    const [count, setCount] = useState(0)
    const [obj, setObj] = useState({a: 1})
    const prevObj = usePrevious(obj)

    // useDeepCompareEffect(() => {
    //     console.log('excuted')
    // }, [obj])

    useEffect(() => {
        prevObj && prevObj.a < obj.a && console.log('do something')
    }, [obj])

    return (
        <div>
            {count}
            <button onClick={() => {setObj({a: obj.a + 1})}}>click</button>
        </div>
    )
}

export default ClickCount

这里用useRef保存了之前的数据, useEffect中的依赖以然为引用类型, 每次obj发生改变都会调用执行函数,但是执行函数中多了一个判断, prevObj是上一次渲染时obj的值, 用prevObj中的某个key与此次obj中的某个key做对比,满足条件后做其他操作,这也是解决方法之一。

但是个人更推荐第一种,直接使用现有的库,更加方便
但是相对的,第一种方案,有可能是进行了深比较,可能是用递归的方式进行比较的,那么当出现 {a: {b: {c: {d: 1}}}}这种多层嵌套的情况时,性能有可能会不那么好。故而还需看具体的应用场景。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值