魯斯前端布魯斯前端

文章中英模式

布魯斯前端React面試題目 - Hook原理與為何Hook呼叫方式有限制?

深入了解React Hooks的運作原理及為何需要遵循特定呼叫規則。了解Hooks在React中如何記住狀態、為何不能在條件式中使用,以及useState、useEffect和useContext的內部實現原理,含面試考點與答案。

影片縮圖

懶得看文章?那就來看影片吧

React Hooks內部實現原理

React在每次渲染時都會呼叫函式元件,但Hook狀態如何保存呢?秘密在於React在元件外部維護一個資料結構(通常是陣列或鏈表)來持久化狀態。

React Hooks的簡化實現模型

// 簡化的Hook實現模型
let hooksArray = []; // 存儲Hook狀態
let currentHookIndex = 0;

// 簡化的useState實現
function useState(initialValue) {
  const index = currentHookIndex;
  
  // 首次渲染時初始化
  if (hooksArray[index] === undefined) {
    hooksArray[index] = initialValue;
  }
  
  // 更新函數
  const setState = (newValue) => {
    hooksArray[index] = newValue;
    rerender(); // 觸發重新渲染
  };
  
  currentHookIndex++;
  return [hooksArray[index - 1], setState];
}

// ---------- 調用示例 ----------
function Counter() {
  // 每次調用都會增加currentHookIndex
  const [count, setCount] = useState(0);
  const [name, setName] = useState("John");
  
  // 如果在條件式中使用Hook
  if (count > 0) {
    // 這會導致索引混亂!
    const [flag, setFlag] = useState(false);
  }
  
  return { count, name, setCount };
}

Hooks內部存儲模型

元件渲染

MyComponent()

執行Hook調用

hooksArray

索引: 0

25

索引: 1

"John"

現在你可以理解為何Hook呼叫順序如此重要:如果在條件式中使用Hook,會導致索引不同步,使得React無法正確回憶每個Hook的先前狀態。

為什麼React Hook有固定的呼叫規則?

React Hook必須遵循兩個主要規則:

  • 1. 只能在React函式元件頂層呼叫Hooks
  • 2. 不能在條件式、迴圈或巢狀函式中呼叫Hooks

這些限制主要是因為React依賴Hook的呼叫順序來識別每個Hook的狀態。React不是用名稱或參數來追蹤Hook,而是靠它們被呼叫的順序

Hook的順序機制

每次渲染時,React按順序存取Hook:

function Profile() {
  // Hook #1
  const [name, setName] = useState("John");
  
  // Hook #2
  const [age, setAge] = useState(25);
  
  // Hook #3
  useEffect(() => {
    document.title = name;
  });
}

React內部存儲(可以理解成是一個List)

Hook #1 (索引 0): [name, setName]
Hook #2 (索引 1): [age, setAge]
Hook #3 (索引 2): [effect函數, 依賴]

如果在條件式中使用Hook會發生什麼?

function Profile(props) {
  const [name, setName] = useState("John"); // 總是Hook #1
  
  if (props.showAge) {
    // 有時是Hook #2,有時不存在!
    const [age, setAge] = useState(25);
  }
  
  // 這個Hook有時是 #2,有時是 #3
  // React會混淆並出錯!
  useEffect(() => {
    document.title = name;
  });
}

React內部存儲的混亂(可以理解成是一個List)

當 props.showAge = true:

Hook #1 (索引 0): [name, setName]
Hook #2 (索引 1): [age, setAge]
Hook #3 (索引 2): [effect函數, 依賴]

當 props.showAge = false:

Hook #1 (索引 0): [name, setName]
Hook #2 (索引 1): [effect函數, 依賴] ❌

⚠️ 當條件改變時,Hook的呼叫順序會變化,React無法正確對應狀態,導致錯誤。

🔥 常見面試題目

(一) 為什麼不能在條件式中使用Hook?

解答: React靠Hook的呼叫順序來記住每個Hook的狀態。就像排隊一樣,每個Hook都有固定位置。

正確用法:

function Counter() {
  const [name] = useState("John"); // 永遠是第1個
  const [count] = useState(0);    // 永遠是第2個
  return <div>{name}: {count}</div>;
}

錯誤用法:

function Counter() {
  const [name] = useState("John");
  
  if (name === "John") {
    const [flag] = useState(false); // 有時存在,有時不存在!
  }
  
  const [count] = useState(0); // 位置會變動!
  return <div>{name}: {count}</div>;
}

Hook順序混亂示意圖

第一次渲染

useState('John') → 位置0

useState(false) → 位置1

useState(0) → 位置2

第二次渲染 (條件不成立)

useState('John') → 位置0

❌ 條件Hook被跳過

useState(0) → 錯拿位置1的值!