茴字的四種寫法——如何在React Hook中得到最新的state

今天的這個問題也源於 生活(工做)😂。在咱們剛開始使用React hook的時候,常常會遇到這樣的狀況:我須要在某個異步請求/事件監聽中更新個人state的值,並拿着更新好的state去作什麼事情。這個時候有可能就會遇到這樣的狀況,state的值並無更新,咱們拿到的老是舊的state。爲何會有這種狀況?咱們有哪些方法能夠來解決它?本文將會帶你解決這些問題。

1. 問題重現

首先來個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

2. 四種解法

2.1 useEffect去掉依賴的空數組

這種解法是最簡單的,去掉空數組便可。這樣,每次useEffect都會運行,儘管仍是個閉包,可是每次都拿了最新的scrollCount值,因此handleScroll的輸出也會持續更新。github

有些同窗可能會問:事件被反覆被加綁和解綁沒有問題嗎?數組

沒有問題,因爲useEffect會在瀏覽器完成佈局與繪製以後調用,且加綁和解綁事件的性能開銷很小,因此這並非問題。瀏覽器

⚠️注意:記得解綁事件,持續的添加事件綁定確定會有問題閉包

useEffect(() => {
    document.addEventListener("scroll", handleScroll);
    return () => document.removeEventListener("scroll", handleScroll);
  // ⚠️改動了這裏:去掉了useEffect的第二個參數,空數組
  });

2.2 將函數移入到useEffect以內,並添加依賴的state

由於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]);

2.3 使用setState的函數式更新

函數式更新:若是新的 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
...

2.4 使用全局變量

回顧這個問題,無非是某個狀態不能得到最新的,咱們使用全局變量就能解決這個問題,不管是把這個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
...

3. 總結

在本文中,咱們探討了問題發生的緣由和4種解決React Hook種得到最新state的辦法。問題自己並不複雜,可是深刻了解其中的原理,並不斷進行總結,纔是咱們持續不斷進步的源泉。

相關文章
相關標籤/搜索