我們建立了 UI = f(state) 的心智模型。但許多新手常遇到這個經典問題:「明明修改了 State 裡的物件,畫面為什麼完全沒更新?」答案不在 React,而在 JavaScript 的記憶體模型。
JavaScript 的資料在記憶體中的存放方式不同:
Number, String, Boolean 等,直接存在 Stack 中。複製時會複製「值」本身,兩者互不影響。Object, Array, Function 等,內容放在 Heap 中,Stack 只存放指向該位址的記憶體指標 (Reference)。let userA = { name: 'Alice', age: 20 }; // 指向位址 0x001
let userB = userA; // 複製指標,同樣指向 0x001
userB.age = 21; // 修改 0x001 的內容
console.log(userA.age); // 21 (userA 也被改到了)
看這段錯誤寫法:
// 錯誤示範:直接修改原物件 (Mutation)
const [user, setUser] = useState({ name: 'Alice', age: 20 });
const handleBirthday = () => {
user.age = 21; // 修改原本位址 (0x001) 的內容
setUser(user); // 傳入同一個記憶體指標 (0x001)
};
React 的檢查機制:Object.is()
setUser 被呼叫時,React 會使用 Object.is(oldValue, newValue) 進行淺比較 (Shallow Compare)。對物件而言,React 只比較記憶體位址:
Object.is(0x001, 0x001) ➔ true
React 認定「資料沒有變」,為了節省效能直接跳過重新渲染 (Skip Re-render)。
不可變性的核心原則:永遠不要直接修改舊資料,而是產生一個全新位址的物件或陣列。
// 正確示範:建立全新的記憶體參考 (New Reference)
const [user, setUser] = useState({ name: 'Alice', age: 20 });
const handleBirthday = () => {
setUser({
...user, // 展開舊屬性
age: 21 // 覆蓋新屬性
}); // 產生新物件,位址變為 0x002
};
此時 Object.is(0x001, 0x002) ➔ false,React 判定資料變更,成功觸發 Re-render!
