حالا چرا در ریاکت میگوییم state را mutate نکن؟
برگرد به مفهوم snapshot که در پیام قبلی گفتم. ریاکت برای اینکه بفهمد state عوض شده، به هویت مرجع (reference identity) نگاه میکند، نه به محتوایش.
بگذار با کد نشان بدهم:
const [todos, setTodos] = useState([{ id: 1, text: 'خرید' }]);
function addTodo() {
// این غلط است
todos.push({ id: 2, text: 'ورزش' });
setTodos(todos);
}
return (
<ul>
{todos.map(t => <li key={t.id}>{t.text}</li>)}
</ul>
);
}چه اتفاقی میافتد وقتی addTodo اجرا شود؟
1. todos.push(...) آرایهی فعلی را در جای خودش تغییر میدهد.
2. حالا todos هنوز همان مرجع قبلی است. همان آدرس حافظه. فقط محتوایش عوض شده.
3. setTodos(todos) صدا زده میشود، اما todos همان reference قبلی است.
4. ریاکت میگوید: «مرجع عوض نشده. پس این state تغییر نکرده.» و رندر دوباره را انجام نمیدهد.
5. UI آپدیت نمیشود. آیتم جدید نمایش داده نمیشود.
این باگ کلاسیک mutation است. کد بهنظر درست کار میکند اما UI عوض نمیشود.
۴. نسخه درست همان کد
const [todos, setTodos] = useState([{ id: 1, text: 'خرید' }]);
function addTodo() {
// این درست است
setTodos([...todos, { id: 2, text: 'ورزش' }]);
}
return (
<ul>
{todos.map(t => <li key={t.id}>{t.text}</li>)}
</ul>
);
}اینجا چه کردیم؟
· [...todos, newItem] یک آرایه جدید ساخت. آرایه قبلی دستنخورده مانده.@perfino_com
· این آرایه جدید مرجع جدیدی دارد.
· setTodos این مرجع جدید را میگیرد.
· ریاکت میبیند مرجع عوض شده، پس رندر دوباره میکند.
· UI آپدیت میشود.
این تفاوت بین mutation و immutable update است.