理解 React Hooks 的 Capture Value 特性

因爲剛使用 React hooks 不久,對它的脾氣還拿捏不許,掉了不少次「坑」;這裏的 「坑」 的意思並非說 React hooks 的設計有問題,而是我在使用的時候,由於尚未跟上它的理念致使的一些問題。html

在讀了一些文章後,大體是找到本身老是掉坑的緣由了 —— 沒理解 React Hooks 中的 Capture Value 特性。react

本文就以簡單的示例來解釋這個特性所產生的現象,對理解 Capture Value 特性作一個補充。git

參考文章

  • Using the Effect Hook : 官方的 useEffect 使用教程,用例詳實 ,附 useEffect - API文檔
  • 精讀《useEffect 徹底指南》:若是你想用好 Function Component 或者 Hooks,這篇文章幾乎是必讀的,由於沒有人能猜到什麼是 Capture Value,然而不能理解這個概念,Function Component 也不能用的順手。
  • 精讀《Function VS Class 組件》 :之後在 React 中常用 Class 的寫法,在 React Hooks 須要轉換成函數式編程風格,這篇文章對比了兩種寫法上的差別;(這兩種寫法沒有好壞之分,性能差距也幾乎能夠忽略,並且 React 會長期支持這兩種寫法)

一、狀態值爲何不是最新的?

「這個 effects 取的值怎麼不是最新的?!」這個疑惑能夠說是在使用 React Hooks 時常常遇到的疑問。github

在下列代碼中,當你點擊按鈕 3s 後,alert 顯示的數值倒是 3s 前的 count 變量 —— 即沒法獲取最新的值,獲取的值是過去某個時刻的:web

import React, { useState, useCallback } from "react";
 import ReactDOM from "react-dom";
 
 function Example() {
   const [count, setCount] = useState(0);
 
   const handleAlertClick = useCallback(()=>{
     setTimeout(() => {
       alert('You clicked on: ' + count);
     }, 3000)
   }, [count]);
 
   return (
     <div>
       <p>You clicked {count} times</p>
       <button onClick={() => setCount(count + 1)}>
         增長 count
       </button>
       <button onClick={handleAlertClick}>
         顯示 count
       </button>
     </div>
   );
 }
 const rootElement = document.getElementById("root");
 ReactDOM.render(<Example />, rootElement);
示例代碼: https://codesandbox.io/s/k5pm...

具體操做步驟編程

  • 當咱們先點擊 顯示 按鈕,在 3s 後(模擬耗時任務)會出現彈層
  • 在這 3s 期間快速點擊 增長 count 按鈕
  • 3s 後看到的彈層計數仍舊爲 0.

show count

二、解釋

這是官方特地設置的機制,官方原文是:This prevents bugs caused by the code assuming props and state don’t change;(強行翻譯一下,大概意思是:防止因 React 認爲 props 或者 state 沒有變動而引發的 bugsegmentfault

爲了理解官方這麼設定的意圖,將上面代碼稍微修改一下:微信

  • 去掉 顯示 count 按鈕
  • 增長一個 減小 count 的按鈕
  • 使用 useEffect 代替 useCallback,讓每次更改 count 都會彈窗
...
useEffect(()=>{
    setTimeout(() => {
      alert('count: ' + count);
    }, 3000)
  }, [count]);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        增長 count
      </button>
      <button onClick={() => setCount(count - 1)}>
        減小 count
      </button>
    </div>
  );
}
...

咱們先點擊一次 增長 count,而後再緊接着點擊一次 減小 countdom

  • 若是不是按照官方的機制設置,那麼咱們看到的兩次彈層顯示的 count 數值都是 0 —— 很明顯這不是咱們想要的
  • 還好實際狀況不是這樣,會先顯示 1,而後顯示 0

calc

總結起來,這個現象其實就是文章 精讀《useEffect 徹底指南》 所說起的 Capture Value 特性(能夠自行前往原文了解更多細節)ide

三、擴展:如何獲取即刻的 count 變量

回到原來的問題,倔強如我,我就是想要在 3s 後獲取的是此時此刻的 count 變量,而不是我 3s 前點擊時的 count,該怎麼操做?

官方給出的解決方案是,每次改變 count 的時候,將其放在 ref 類型的變量裏便可。

修改一下原來的代碼:

const countRef = useRef(null);
  const handleAlertClick = useCallback(
    () => {
      setTimeout(() => {
        alert("You clicked on: " + countRef.current);
      }, 3000);
    },
    [count]
  );

  return (
    <div>
      <p>You clicked {count} times</p>
      <button
        onClick={() => {
          countRef.current = count + 1;
          setCount(count + 1);
        }}
      >
        增長 count
      </button>
      <button onClick={handleAlertClick}>顯示 count</button>
    </div>
  );

更改事後的代碼運行後,3s 後 alert 顯示的 count 變量就是你頁面上所見到的樣子了:

count

ref 類型的變量一般是用來存儲 DOM 元素引用,但在 react hooks 中,它能夠存聽任何可變數據,就比如類實例屬性同樣,具體參考 Is there something like instance variables?

這等操做,其實就是藉助 ref 類型變量繞過 Capture Value 特性來達到目的。

四、總結

援引文章 精讀《useEffect 徹底指南》 中對 Capture Value 概念的解釋:每次 Render 的內容都會造成一個快照並保留下來,所以當狀態變動而 Rerender 時,就造成了 N 個 Render 狀態,而每一個 Render 狀態都擁有本身固定不變的 Props 與 State

經過這個示例,相信會比較容易地理解 Capture Value 特性,並如何使用 ref 來暫時繞過它。在知道並理解這個特性後,有助於進一步熟悉了 React Hooks 的運行機制,減小掉坑的次數。

這裏羅列幾篇文章,方便自檢是否掌握了這個概念:

下面的是個人公衆號二維碼圖片,歡迎關注。
我的微信公衆號

相關文章
相關標籤/搜索