今天的這個問題也源於生活(工做)😂。在咱們剛開始使用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的辦法。問題自己並不複雜,可是深刻了解其中的原理,並不斷進行總結,纔是咱們持續不斷進步的源泉。