useEffect使用指南

React项目中如果使用函数式组件进行开发时,如果想在不使用class组件的情况下使用state和其他React特性,Hook将是你的不二选择。

而Effect Hook又是一种比较常见的Hook,可以在函数式组件中执行副作用操作。刚开始可以理解为created、update生命周期。

一、为effect添加依赖

    const [count, setCount] = useState(0)

    useEffect(() => {
        const fn = setTimeout(() => {
            console.log(count)
            setCount(count+1)。
        }, 1000)
    })
    // 0
    // 1
    // 2

    useEffect(() => {
        const fn = setTimeout(() => {
            console.log(count)
            setCount(count+1)
        }, 1000)
    },[])
    // 0

effect的执行机制,是比较两次依赖项是否相同,不同则执行相关effect。

如果不替effect添加依赖,则是比较两次effect。而我们知道在函数式组件中,每次渲染的state、effect都是不同的。只要组件内部状态发生变化就会执行effect。

而将依赖项设置为一个固定值,则effect只会初始化时执行一次。

二、effect内部状态自调用

上面两种情况是比较简单的,在复杂的业务逻辑中,我们往往需要依据某个状态的变化然后再来执行副作用操作,并且还需要为effect设置清除函数。我们看看下面这段代码:

    useEffect(() => {
        const fn = setTimeout(() => {
            console.log(count)
            setCount(count+1)
        }, 1000)
        return () => clearTimeout(fn)
    },[count])
    // 0
    // 1
    // 2

我们在effect中添加了清除函数,并且需要将count作为依赖在count变化时执行内部代码。这应该是大家使用Hook Effect比较常见的用法。

但是就这个例子而言,这种设置依赖的写法是有逻辑漏洞的。在effect内部只需要将count做递增,而此时React是知道count的值,所以effect并不要使用count,可以使用setState的函数形式代替传参。

    useEffect(() => {
        const fn = setTimeout(() => {
            console.log(count)
            setCount(c => c+1)
        }, 1000)
        return () => clearTimeout(fn)
    })
    // 0
    // 1
    // 2

setCount(c => c+1)就是在方法内部发送更新指令,告诉React去更新state的值,这样就将count从effect依赖中去掉,保持内部逻辑干净无污染。

三、将函数放在effect内部

有时一个effect内部函数代码量过多,我们可以将代码进行分割单独封装成函数,使整个逻辑看起来清晰明了。可能我们为了进一步追求代码规范,需要将函数抽离放在effect外部。

    const countPlayer = () => {
        return setTimeout(() => {
            setCount(count+10)
        },1000)
    }

    useEffect(() => {
       const fn = countPlayer()
       return () => clearTimeout(fn)
    },[])
    console.log(count)
    // 0
    // 10

比如我们要在初始化时通过接口获取数据进行setState赋值然后渲染UI,并且获取数据操作只需要执行一次。上面的代码显示count为0时执行了effect副作用,通过countPlayer函数进行组件state初始化操作。第二次由于依赖没有变更就跳过了直接往后执行。

逻辑上看起来没问题,但是如果当代码量过大并且存在多个函数嵌套依赖的情况,就很难保证准确更新相关effect依赖。并且如果项目安装了eslint-plugin-react-hooks,在项目打包运行时就会出现警告。

 这种情况,我们将函数移动到effect内部就能很好地规避这个问题。

四、将函数放在effect外部

如果一个函数在多个effect中被复用,这时我们就必须将函数放在effect外部了。至于逻辑漏洞和代码eslint告警的问题,一般有两种方法解决:

  • 将函数转化成纯函数,移动到组件外部;
  • 使用useCallback Hook进行包装;

如果一个函数没有使用组件内部任何state、props就可以转化成纯函数,在不同的effect中任意调用。

如果函数内部需要对state等进行setState操作,可以使用useCallback将整个函数包装成一个依赖state的可变值,然后作为effect的依赖使用。

可能有的小伙伴会问,为啥不直接将countPlayer函数作为依赖呢?下面我们看看这样设置后的运行情况。

    const countPlayer = () => {
        return setTimeout(() => {
            setCount(count+10)
        },1000)
    }

    useEffect(() => {
       const fn = countPlayer()
       return () => clearTimeout(fn)
    },[countPlayer])
    console.log(count)
    // 0
    // 10
    // 20
    // ...

会出现无限循环自调用的bug。之前我们提过,在函数式组件内部,每次获取的函数体、state、effect都不一样,所以会导致effect陷入死循环。下面我们将函数用useCallback进行包装看看效果。

    const countPlayer = useCallback(() => {
        return setTimeout(() => {
            setCount(c => c+10)
        },1000)
    },[])

    useEffect(() => {
       const fn = countPlayer()
       return () => clearTimeout(fn)
    },[countPlayer])
    console.log(count)
    // 0
    // 10

这样既满足了将函数体抽离出effect的目的;并且通过useCallback包装使函数体本身成为了一个拥有独立依赖的可变值,可以在不同的effect之间复用。

至此,effect Hook一些常用的思路和使用技巧都讲解完毕!

 

posted on 2020-09-13 23:20  世界之魂  阅读(5110)  评论(0编辑  收藏  举报

导航