今天的这个问题也源于生活(工做)😂。在咱们刚开始使用React hook的时候,常常会遇到这样的状况:我须要在某个异步请求/事件监听中更新个人state的值,并拿着更新好的state去作什么事情。这个时候有可能就会遇到这样的状况,state的值并无更新,咱们拿到的老是旧的state。为何会有这种状况?咱们有哪些方法能够来解决它?本文将会带你解决这些问题。
首先来个demo,以下:javascript
import React from "react"; import "./styles.css"; const { useState, useEffect } = React; export default function App() { const [scrollCount, setScrolledCount] = useState(0); useEffect(() => { console.log(`useEffect: ${scrollCount}`); }); const handleScroll = () => { console.log(`handleScroll: ${scrollCount}`); setScrolledCount(scrollCount + 1); }; useEffect(() => { document.addEventListener("scroll", handleScroll); return () => document.removeEventListener("scroll", handleScroll); // ⚠️注意:这里是空数组,表明了只有在组件挂载时运行一次 }, []); return ( <div className="App"> <h1>React hook得到最新state Demo</h1> </div> ); }
在这个例子中,咱们监听了滚动事件,而后持续的将scrollCount
加1,同时持续输出scrollCount
的值。毫无疑问,咱们预想的结果应该是:css
useEffect: 0 handleScroll: 0 useEffect: 1 handleScroll: 1 useEffect: 2 handleScroll: 2 ...
可是实际的结果是:html
useEffect: 0 handleScroll: 0 useEffect: 1 handleScroll: 0 // 由于state的值没有被更新,因此组件没有被从新渲染,useEffect也没有运行 handleScroll: 0 handleScroll: 0 ...
那么为何会出现这样的拿不到最新的state的状况呢?java
由于useEffect执行时,会建立一个闭包,因此在运行的时候的scrollCount的值被保存在这个闭包中,且初始值为0,因此当滚动时,每次都输出0
❗️React hook相关原理能够参照:《React Hook原理》——这是我以为讲的最清楚的一篇,没有之一。react
那怎么样解决这个问题呢?git
这种解法是最简单的,去掉空数组便可。这样,每次useEffect都会运行,尽管仍是个闭包,可是每次都拿了最新的scrollCount值,因此handleScroll的输出也会持续更新。github
有些同窗可能会问:事件被反复被加绑和解绑没有问题吗?数组
没有问题,因为useEffect会在浏览器完成布局与绘制以后调用,且加绑和解绑事件的性能开销很小,因此这并非问题。浏览器
⚠️注意:记得解绑事件,持续的添加事件绑定确定会有问题。闭包
useEffect(() => { document.addEventListener("scroll", handleScroll); return () => document.removeEventListener("scroll", handleScroll); // ⚠️改动了这里:去掉了useEffect的第二个参数,空数组 });
由于useEffect依赖了scrollCount,因此每次scrollCount的改动都会使得useEffect从新运行,从而得到了当前最新的scrollCount。
固然了,须要强调的是,函数移不移入useEffect以内其实并无影响,只不过移入了以后,你会更容易的看到到底函数中依赖了哪一个state。
useEffect(() => { // ⚠️改动了这里:handleScroll被移入了useEffect内部 const handleScroll = () => { console.log(`handleScroll: ${scrollCount}`); setScrolledCount(scrollCount + 1); }; document.addEventListener("scroll", handleScroll); return () => document.removeEventListener("scroll", handleScroll); // ⚠️改动了这里:空数组变为[scrollCount] }, [scrollCount]);
函数式更新:若是新的 state 须要经过使用先前的 state 计算得出,那么能够将函数传递给
setState
。该函数将接收先前的 state,并返回一个更新后的值。来自
React官方文档
const handleScroll = () => { console.log(`handleScroll: ${scrollCount}`); // ⚠️改动了这里:表达式变成了函数 setScrolledCount(scrollCount => scrollCount + 1); };
经过使用这个特性,咱们能够保证state每次都被更新了,可是handleScroll中得到的scrollCount
值仍是闭包中的0。咱们实际获得的输出以下:
useEffect: 0 handleScroll: 0 useEffect: 1 handleScroll: 0 useEffect: 2 handleScroll: 0 ...
回顾这个问题,无非是某个状态不能得到最新的,咱们使用全局变量就能解决这个问题,不管是把这个state挂载到window下,仍是使用useRef,都能得到最新的值。
这种改动代码变化稍大,总体贴在下面:
import React from "react"; import "./styles.css"; const { useRef, useEffect } = React; export default function App() { const scrollCountRef = useRef(null); useEffect(() => { console.log(`useEffect: ${scrollCountRef.current}`); }); const handleScroll = () => { console.log(`handleScroll: ${scrollCountRef.current}`); scrollCountRef.current++; }; useEffect(() => { scrollCountRef.current = 0; document.addEventListener("scroll", handleScroll); return () => document.removeEventListener("scroll", handleScroll); }, []); return ( <div className="App"> <h1>React hook得到最新state Demo</h1> </div> ); }
固然,这种代码也不涉及到组件的从新渲染,因此它的输出是:
useEffect: null handleScroll: 1 handleScroll: 2 handleScroll: 3 ...
在本文中,咱们探讨了问题发生的缘由和4种解决React Hook种得到最新state的办法。问题自己并不复杂,可是深刻了解其中的原理,并不断进行总结,才是咱们持续不断进步的源泉。