中文字幕精品亚洲无线码二区,国产黄a三级三级三级看三级,亚洲七七久久桃花影院,丰满少妇被猛烈进入,国产小视频在线观看网站

【每日一(yi)面】React Hooks閉(bi)包陷阱(jing)

基礎問答

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

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

閉包的本質:函數能夠(gou)訪問其定義時(shi)所在的詞法(fa)作用(yong)域,即使函數在作用(yong)域外(wai)執行(xing),也可以記住定義時(shi)的詞法(fa)作用(yong)域的內(nei)容,后續執行(xing)時(shi),使用(yong)這(zhe)些信息。

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

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

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

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

閉包陷阱:根據上面兩點,React Hooks 的閉包陷阱產生過程應當是這樣的,React 在渲染開始前創建了新的狀態包(作用域),而我們寫代碼的時候無意中創建了一個閉包,持有了 React 的當前狀態,再下次渲染開始時,React 重新創建了狀態包,但是我們在一開始創建的閉包持有的依舊是前一次 React 創建的狀態,是舊的,這就是產生閉包陷阱的根源。這里(li)我們(men)以(yi)一個具體例(li)子來(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 幫我(wo)們設(she)置了一(yi)個定時(shi)器(qi),定時(shi)器(qi)執(zhi)行的(de)函數持有了外部作用域(yu)的(de) count 變(bian)量,產生了一(yi)個閉包。

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

閉包陷阱破解方式

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

擴展知識

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

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;
}

示例中存(cun)在 4 個 hooks,所以 React 收集完(wan)成后形成的(de)鏈表應當(dang)是(shi)這樣的(de):

鏈表圖

React 為鏈表節點設(she)計(ji)了(le)如(ru)下數(shu)據結(jie)構:

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

其中 next 就(jiu)是鏈表(biao)節點(dian)用(yong)于指向下一個節點(dian)的指針,memoizedState 則(ze)是上一次更(geng)新(xin)后的相關 state。組件更(geng)新(xin)的時(shi)候,hooks 會(hui)嚴(yan)格按(an)照這個順序進行執行,按(an)順序拿到(dao)對(dui)(dui)應的 Hook 對(dui)(dui)象,所以如(ru)果(guo)我們用(yong) if else 包裹了其中一個 hook,就(jiu)會(hui)出現(xian)鏈表(biao)執行過(guo)程中,Hooks 對(dui)(dui)象取值錯誤的情況(kuang)。

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

面試追問

  1. 能手寫一個閉包嗎?

參考前文代碼。

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

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

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

React 是以鏈表形式來(lai)組織管理 hooks 的,在收集(ji)過程中(zhong)按(an)照順序組裝(zhuang)成(cheng)鏈表,然(ran)后(hou)每次觸發狀態更新時,會(hui)從鏈表頭開始依次判斷執行更新。

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

可以理解為異(yi)步的,展開來說,則是: state 更新(xin)函數(shu)(如觸發 setCount)是同步觸發的,React 執行更新(xin)(即 count 被更新(xin))是異(yi)步的。這種設(she)計主要是出于性能(neng)考慮,避免重(zhong)(zhong)(zhong)復渲染(ran),減少重(zhong)(zhong)(zhong)繪(hui)重(zhong)(zhong)(zhong)排。

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

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

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

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