文章中英模式
布魯斯前端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會發生什麼?
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:
當 props.showAge = false:
⚠️ 當條件改變時,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的值!