欧美精产国品一二三区,国产成人一区二区三区A片免费,特级毛片www免费版,成人做爰A片免费看黄冈宾馆,日韩精品人妻中文字幕有码

【每(mei)日一面】React Hooks閉包陷阱

基礎問答

問題:談一談你對 React Hook的閉包陷阱的理解。

產生問題的原因:JavaScript 閉包特性 + Hooks 渲染(ran)機制

閉包的本質:函(han)數能夠訪問其(qi)定義(yi)(yi)時所(suo)在的(de)詞(ci)法(fa)作用域(yu),即使函(han)數在作用域(yu)外執行,也可以記(ji)住定義(yi)(yi)時的(de)詞(ci)法(fa)作用域(yu)的(de)內容,后續執行時,使用這些信息(xi)。

function callback(index) {
  let idx = index;
  let op;
  return (type) => {
    op = type;
    console.log(op);
    switch (type) {
      case 'add':
        idx++;
        break;
      case 'sub':
        idx--;
        break;
    }
    return idx;
  }
}

const fn = callback(8);
console.log(fn('add')); // 9
console.log(fn('sub')); // 8

這里的 idx 正常會(hui)在(zai) callback 函(han)(han)數執行結束后釋放,但是(shi)由于我們返回的是(shi)一(yi)個函(han)(han)數,函(han)(han)數中依(yi)賴這個 idx 變量,所(suo)以未能釋放,此時這個變量被(bei)這個匿名函(han)(han)數持(chi)有(you),而在(zai) fn 變量存(cun)續期間,idx 和 op 都是(shi)不會(hui)釋放的,這也就形成(cheng)了一(yi)個閉包。

不過(guo)經典閉包還是 for 循(xun)環

Hooks 渲染邏輯:React 組件每次渲染都是獨立的快照,可以理(li)解為,每次重新執行相關鉤子的時候,組(zu)件都會重新生成一個新的作用域。

閉包陷阱:根據上面兩點,React Hooks 的閉包陷阱產生過程應當是這樣的,React 在渲染開始前創建了新的狀態包(作用域),而我們寫代碼的時候無意中創建了一個閉包,持有了 React 的當前狀態,再下次渲染開始時,React 重新創建了狀態包,但是我們在一開始創建的閉包持有的依舊是前一次 React 創建的狀態,是舊的,這就是產生閉包陷阱的根源。這里我們(men)以一個具體例子來(lai)看:

import { useEffect, useState } from "react"

const App = () => {
  const [count, setCount] = useState(1);

  useEffect(()=> {
    const timer = setInterval(() => console.log(count), 1000);
    return () => clearInterval(timer)
  }, []);

  const addOne = () => {
    setCount(pre => pre+1);
  }

  return (
    <div className="main" > 
      <p>Hello: {count}</p>
      <button onClick={addOne}>+1</button>
    </div>
  )
}

export default App

這里在組件首次渲染的時候,useEffect 幫我們設置了(le)(le)一個定(ding)時器(qi),定(ding)時器(qi)執行的函(han)數持有了(le)(le)外部作用域的 count 變量,產生了(le)(le)一個閉包(bao)。

再之后,我們在頁面上點擊按鈕時,觸發了 setCount(pre => pre+1) 狀態更新,但是由于沒有配置 useEffect 的(de)更(geng)新依(yi)賴,所以定時(shi)器(qi)還是持有(you)舊的(de)狀態包。此時(shi)打印的(de)還是 1,沒有(you)更(geng)新。

閉包陷阱破解方式

  1. 使用 useRef:useRef 在初始化后,是一個形如 { current: xxx } 的不可變對象,不可變可以理解為,這個對象的地址不會發生變化,所以在淺層次的比較(===)中,更新后的前后對象是一個。所以取值的時候,總是能拿到最新的值。
  2. 添加 Hooks 依賴:在 useEffect 鉤子的依賴列表中增加 count,當 count 發生變化的時候,會重新執行 useEffect ,內部的 timer 會重新生成,拿到最新的作用域的值。
  3. 修改 state 為一個對象:類似于 useRef,我們在更新 state 的時候,可以直接把內容寫入該對象中,避免直接替換 state 對象。

擴展知識

React 官方要(yao)求我們不(bu)能(neng)將 hooks 用 if 條件判(pan)斷包裹,其原因是(shi) React 的(de)(de) Fiber 架(jia)構(gou)中收(shou)集 Hooks 信息的(de)(de)時候是(shi)按順(shun)序收(shou)集的(de)(de),并以鏈表的(de)(de)形式進行存儲的(de)(de)。如下示例:

function App() {
  const [count, setCount] = useState(0);
  const [isFirst, setIsFirst] = useState(false);

  useEffect(() => {
    console.log('hello init');
  }, []);

  useEffect(() => {
    console.log('count change: ', count);
  }, [count]);

  const a = 1;
}

示例中存在(zai) 4 個 hooks,所以 React 收(shou)集完(wan)成(cheng)后形(xing)成(cheng)的(de)鏈表應當是這(zhe)樣的(de):

鏈表圖

React 為鏈表節(jie)點設計了如下數據結構:

type Hook = {
  memoizedState: any,
  /** 省略這里不需要的內容 */
  next: Hook | null,
};

其(qi)中 next 就(jiu)是鏈(lian)(lian)表(biao)節點用于指向(xiang)下一(yi)個(ge)節點的(de)指針,memoizedState 則是上一(yi)次更新(xin)后的(de)相(xiang)關 state。組件更新(xin)的(de)時候,hooks 會(hui)嚴格(ge)按(an)照這個(ge)順(shun)序進(jin)行(xing)執行(xing),按(an)順(shun)序拿到對應(ying)的(de) Hook 對象(xiang),所(suo)以如果我們用 if else 包裹了其(qi)中一(yi)個(ge) hook,就(jiu)會(hui)出現鏈(lian)(lian)表(biao)執行(xing)過程中,Hooks 對象(xiang)取(qu)值錯誤的(de)情況。

同樣的,React 官方告訴我們,如果想在更新的時候拿到當前 state 的值,建議使用回調函數的寫法,即:setCount(pre => pre + 1) 這種寫法,這個(ge)原因(yin),通過(guo) Hook 的數據結構也大致(zhi)可(ke)以判斷(duan),因(yin)為 memoizedState 存(cun)儲了前一次更新的數據,使用回(hui)調時,這個(ge) memoizedState 就可(ke)以作為參數提供給(gei)我(wo)們,并且保(bao)證(zheng)總(zong)是正確的。

面試追問

  1. 能手寫一個閉包嗎?

參考前文代碼。

  1. 使用 useRef 存儲值,會有什么問題?

useRef 在初始化后,是形如 { current: xxx } 的對象,這個對象地址不會變化,所以我們監聽 ref 是不起作用的,同時,和 useState 不同,useRef 內容(rong)的變更不會觸發組件重新渲染(ran)。

  1. 請談談 hooks 在 React 中的更新邏輯?

React 是以鏈(lian)表(biao)形式來組織(zhi)管理 hooks 的(de),在收集(ji)過程(cheng)中按照(zhao)順序組裝(zhuang)成鏈(lian)表(biao),然后每次(ci)觸發(fa)狀態(tai)更新時,會從鏈(lian)表(biao)頭開始(shi)依(yi)次(ci)判(pan)斷執行更新。

  1. 那 hooks 中,useState 的更新是同步還是異步?

可以理解(jie)為異步的,展開來說(shuo),則(ze)是: state 更新(xin)(xin)函數(如(ru)觸發 setCount)是同(tong)步觸發的,React 執行更新(xin)(xin)(即 count 被(bei)更新(xin)(xin))是異步的。這種(zhong)設計主要(yao)是出(chu)于性能考慮(lv),避(bi)免重(zhong)復渲染,減少重(zhong)繪重(zhong)排。

  1. useEffect 依賴數組傳空數組和不傳依賴,二者有什么區別?

空數組:effect 僅在組件(jian)首次渲染時執行一次,后(hou)續不會再執行,相(xiang)當于(yu)組件(jian)掛載階段(duan)。

不傳依賴:effect 會在組(zu)件首次(ci)(ci)渲染(ran)時、每次(ci)(ci)重(zhong)新(xin)渲染(ran)后都執行(xing)。這種形式隱(yin)含存(cun)(cun)在渲染(ran)循(xun)環的風險,即 effect 中存(cun)(cun)在修改 state 的操作,那(nei)么按(an)照不傳依賴時執行(xing)的規則,就會陷入渲染(ran) -> 更新(xin) -> 觸發重(zhong)渲染(ran) -> 更新(xin) -> 觸發重(zhong)渲染(ran)……這樣(yang)的循(xun)環。

posted @ 2025-09-26 16:44  Achieve前端實驗室  閱讀(169)  評論(2)    收藏  舉報