React Hooks的基本用法,官方文档 已经很是详细。本文的目的,是想经过一个简单的例子详细分析一些使人疑惑的问题及其背后的缘由。这是系列的第二篇,主要讲解 useEffect。javascript
我的博客地址 🍹🍰 fe-codehtml
官方文档中说,能够将 useEffect 的回调和清理反作用的机制,类比成 class 组件中的生命周期。不过,因为 class 组件和函数组件自身特性不一样的缘由,致使这种类比也容易令人迷惑。前端
若是你熟悉 React class 的生命周期函数,你能够把 useEffect Hook 看作 componentDidMount,componentDidUpdate 和 componentWillUnmount 这三个函数的组合。 -- 使用 Effect Hook
不过有时也容易出问题,就像咱们一开始的定时器例子同样。java
function Counter() { const [count, setCount] = useState(0); useEffect(() => { const id = setInterval(() => { setCount(count + 1); }, 1000); }, []); return <h1>{count}</h1>; }
咱们的需求很明确,就是在 componentDidMount 的时候,设置一个定时器。而且保证不会每次更新(componentDidUpdate)都从新设置。因此咱们把第二个参数设置成[]
,来达到同样的效果。react
固然这是有问题的,因为函数式组件执行方式的不一样,咱们在 useEffect 中拿到的 count 是闭包引用的,而每次更新又会是一个全新的执行上下文。这在上一篇文章中已经详细分析过。可是在 class 组件中,生命周期中的引用是这样的 this.state.count
,并且不一样于函数式,这种方式每次拿到的 count 都是最新的。git
React Hooks 也提供了一个相似做用的 hook 来帮咱们保存一些值 — useRef,它能够很方便地保存任何可变值,其相似于在 class 中使用实例字段的方式
。不过这里不太适用。github
总的来讲,useEffect 和真正的生命周期仍是有些区别的,在使用的时候须要多加注意。web
经过第一篇文章,咱们已经了解了一些很重要的信息,好比:每次更新都是一次从新执行。这不只仅是对于 useState 来讲的,整个函数组件都是这样。不太了解的同窗,能够先阅读一下 深刻 React hooks — useState。
function Counter() { const [count, setCount] = useState(0); useEffect(() => { const id = setInterval(() => { setCount(count + 1); }, 1000); }, []); return <h1>{count}</h1>; }
咱们知道 每次更新都是一次从新执行。咱们给 useEffect 的第二个参数传的是 []
,因此能够达到回调只运行一次的效果(只设置一次定时器)。数组
可是咱们更应该知道的是,回调函数只运行一次,并不表明 useEffect 只运行一次。在每次更新中,useEffect 依然会每次都执行,只不过由于传递给它的数组依赖项是空的,致使 React 每次检查的时候,都没有发现依赖的变化,因此不会从新执行回调。浏览器
检查依赖,只是简单的比较了一下值或者引用是否相等。
并且上面的写法,官方是不推荐的。咱们应该确保 useEffect 中用到的状态(如:count ),都完整的添加到依赖数组中。 无论引用的是基础类型值、仍是对象甚至是函数。
function Counter() { const [count, setCount] = useState(0); useEffect(() => { const id = setInterval(() => { setCount(count + 1); // 不想用到外部状态能够用 setCount(count => count + 1); }, 1000); }, [count]); // 确保全部状态依赖都放在这里 console.log(count); return <h1>{count}</h1>; }
这样才能保证回调中能够每次拿到当前的 count 值。
咦!好像有什么奇怪的东西。
发生了什么不得了的事???
如今想一想咱们都干了什么。
每次更新时,会从新运行 useEffect 的回调函数,也就会从新设置一个定时器。可是有一个问题是,咱们上一次设置的定时器并无清理掉,因此频繁的更新会致使愈来愈多的定时器同时在运行。
为了解决上面的问题,就须要用到 useEffect 的另外一个特性:清除反作用。
function Counter() { const [count, setCount] = useState(0); useEffect(() => { const id = setInterval(() => { setCount(count + 1); }, 1000); // 返回一个清理反作用的函数 return () => { clearInterval(id); } }, [count]); console.log(count); return <h1>{count}</h1>; }
ok,世界安静了。
那么,再思考个问题吧。useEffect 清理反作用的时机是何时?在下一次视图更新以前吗?
function Counter() { const [count, setCount] = useState(0); useEffect(() => { const id = setInterval(() => { console.log(1, '我是定时器', count); setCount(count + 1); }, 1000); return () => { console.log(2, `我清理的是 ${count} 的反作用`); clearInterval(id); } }, [count]); console.log(3, '我是渲染', count); return <h1>{count}</h1>; }
上面代码的打印顺序会是 一、二、3 吗?
显然不是,useEffect 在视图更新以后才清理上一次的反作用。这么处理其实也是和 useEffect 的特性相契合的。React 只会在浏览器绘制后运行 useEffect。因此 Effect 的清除一样被延迟了。上一次的 Effect 会在从新渲染后被清除。
使用 useEffect 时,须要注意状态的引用,依赖的添加以及反作用的清除(没有就不用了)。不少时候还须要借助其余的 hook 才能完成这个工做,好比 useRef/useCallback等。
微信群:扫码回复加群。
若是你看到了这里,且本文对你有一点帮助的话,但愿你能够动动小手支持一下做者,感谢🍻。文中若有不对之处,也欢迎你们指出,共勉。好了,又耽误你们的时间了,感谢阅读,下次再见!
感兴趣的同窗能够关注下个人公众号 前端发动机,好玩又有料。