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

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

基礎問答

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

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

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

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 正常會在 callback 函(han)數執行結(jie)束后釋放(fang),但是由于(yu)我們返回(hui)的(de)是一(yi)個(ge)函(han)數,函(han)數中依賴這(zhe)個(ge) idx 變(bian)量(liang),所以未能釋放(fang),此時這(zhe)個(ge)變(bian)量(liang)被這(zhe)個(ge)匿(ni)名(ming)函(han)數持有,而(er)在 fn 變(bian)量(liang)存續期間,idx 和 op 都是不會釋放(fang)的(de),這(zhe)也就形成了一(yi)個(ge)閉(bi)包(bao)。

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

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

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

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 幫我們設(she)置了一個定時器,定時器執行的函數持有了外部作用域的 count 變量,產(chan)生了一個閉包。

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

閉包陷阱破解方式

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

擴展知識

React 官(guan)方要求我們(men)不能(neng)將 hooks 用 if 條件判斷(duan)包(bao)裹,其(qi)原因是 React 的 Fiber 架構中收集 Hooks 信息的時候是按(an)順序收集的,并以鏈表(biao)的形式進行存(cun)儲的。如下(xia)示例(li):

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

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

鏈表圖

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

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

其中 next 就是鏈表(biao)節點用(yong)于(yu)指(zhi)向下一(yi)個節點的(de)指(zhi)針(zhen),memoizedState 則是上一(yi)次更新(xin)后的(de)相(xiang)關 state。組件(jian)更新(xin)的(de)時候,hooks 會嚴格按照(zhao)這(zhe)個順序進行執(zhi)行,按順序拿(na)到(dao)對應(ying)的(de) Hook 對象,所以(yi)如果(guo)我們用(yong) if else 包裹了(le)其中一(yi)個 hook,就會出(chu)現鏈表(biao)執(zhi)行過程中,Hooks 對象取(qu)值錯誤的(de)情況。

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

面試追問

  1. 能手寫一個閉包嗎?

參考前文代碼。

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

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

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

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

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

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

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

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

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

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