全面理解useEffect——useEffect用法解析与使用技巧
全面理解useEffect
useEffect可以说是react的hook中最强大的一个,他能够以一种极为优雅的方式模拟我们的生命周期。但是优雅的代价就是这个hook极其的晦涩,不容易被理解与掌握。下面我们就来全方位的探讨一下,useEffect到底怎么用
useEffect的结构
在上一篇关于react的文章我提到过,函数式组件每次reRender都会重新执行一遍,如果我们需要生命周期的效果,那我们就需要useEffect函数。我们先来看一下useEffect的结构:
1 | |
参数
setup:一个实现了你需要的副作用逻辑的函数,这个函数将会在组件首次被绑定以及每次重新渲染导致dependencies更新时调用。同时他还可以return一个清除函数,这个函数将会在函数卸载以及每次重新渲染导致dependencies更新之前调用dependencies:在setup中引用的所有reactive values(state、参数以及在组件内部定义的变量和函数)或更新凭据组成的数组。这个数组必须是有限的并且偏平的,每次更新react将使用Object.is()比较每个依赖项是否与之前相同,如果不同则更新。
return
undefinded(无返回值)
中文博客中经常会漏掉setup函数的返回值,但是它的返回值是这个函数的半壁江山,我们决不能将其忽略不提,如果忽略掉的话我们就有一半的效果无法实现了。
❗:useEffect也是一个hook,请在组件或者自定义hook的最上层使用,不要在循环或分支结构中定义hook
模拟生命周期
react生命周期大概分为三种,mount、update、unmount,当然在类式组件,我们已经可以规定是在mount之前,mount时还是mount之后调用,在函数式组件我们就不分的这么清楚了,因为在函数式组件我们的生命周期其实是独立的,是面向状态的。
下面我们就使用useEffect简单模拟一下类式组件的生命周期:
只在初始化时执行(DidMounted) :
1 | |
每次更新时执行(render) :
1 | |
每次卸载时执行(beforeUnmount) :
1 | |
实际上,我们经常说useEffect的作用是模拟声明周期,而这似乎违背了函数式组件的初衷,也违背了useEffect的初衷。我们应该多从状态的角度去思考,useEffect并非是什么生命周期函数,而是状态更新函数。
控制更新
如果某个变量在每次组件更新的时候都需要更新,那我们完全没有必要使用useEffect,我们使用副作用钩子一定是因为我们需要控制某个变量的更新。
我们可以使用useEffect将组件内部的状态与组件外部系统同步:
1 | |
或者我们可以使用useEffect将组件组件外部系统与内部的状态同步:
1 | |
其实生命周期也不过是对状态的统一更改,react现在只是将生命周期将颗粒度从组件降低到了状态。
一些tips
在useEffect中使用state
有时候我们可能会需要在useEffect中使用和修改state,但是这是极端危险的行为,因为当我们修改state时会触发页面的rerender,然后因为useEffect依赖了state,这将会导致state被再次更改继而引起死循环……
1 | |
如果我们在useEffect中根据之前的state更新state,我们可以这么写:
1 | |
如果你真的既要在Effect中读取state,又要修改state,那么我给出的建议是,:使用两个依赖同一个更新凭证useEffect
千万别这么做:
1 | |
正确的做法如下:
1 | |
消除不必要的对象与函数依赖
如果我们在组件内定义了一个函数或对象,我们在上一篇文章详细讲过,它每次都会被初始化。就像这样:
1 | |
取而代之的是,如果你的对象或者函数只在useEffect中使用,则写在setup内部
1 | |
如果你是希望有一个在整个组件都可以被访问的静态变量,那么可以将对象写在组件外部:
1 | |
useLayoutEffect
我们在使用useEffect的时候有时会出现一种问题:我们的组件会发生闪烁或突然的变化。
这时因为useEffect是在mount之后执行的,因此第一次渲染出的值是我们定义的初始值,如果更新的性能比较差或者更新的范围比较大,可能会导致我们的view发生闪烁的现象。
这个时候我们就可以使用useLayoutEffect,他和useEffect的最大区别就是他是在mount之前执行的,所以不会导致view的闪烁。
但是如果我们没有很明显的ui闪烁问题,我们应该尽可能的使用useEffect,因为它的运行效率更好。