استیت در React میتواند هر نوع مقدار جاوااسکریپتی، از جمله اشیاء (Objects) را در خود ذخیره کند. اما یک قانون طلایی وجود دارد: شما نباید اشیایی را که در استیت React قرار دارند مستقیماً تغییر دهید. هر زمان که میخواهید شیء موجود در استیت را بهروزرسانی کنید، باید یک شیء جدید ایجاد کنید (یا یک کپی از شیء فعلی بگیرید) و سپس استیت را با آن کپی جدید مقداردهی کنید.
مفهوم Mutation (تغییر ساختار) چیست و چرا در React ممنوع است؟
نحوه صحیح بهروزرسانی فیلدهای یک شیء با نحو Spread (...)
چگونگی آپدیت اشیاء تودرتو (Nested Objects) بدون دستکاری نسخه اصلی
سادهسازی کدهای کپی با استفاده از کتابخانه Immer
تا اینجا شما با مقادیر اولیه (Primitives) مثل اعداد، رشتهها و بانیولینها کار کردهاید. این نوع مقادیر در جاوااسکریپت Immutable (تغییرناپذیر) هستند؛ یعنی غیرقابل تغییر و «فقط خواندنی» هستند. شما با اجرای setX(5) مقدار جدیدی را جایگزین مقدار قبلی میکنید، اما خودِ عدد 0 در حافظه کامپیوتر تغییری نمیکند.
حالا یک شیء را در استیت در نظر بگیرید:
const [position, setPosition] = useState({ x: 0, y: 0 });
از نظر فنی در جاوااسکریپت، میتوان محتوای خود این شیء را مستقیماً دستکاری کرد. به این کار Mutation (جهش/تغییر ساختار) میگویند:
position.x = 5; // این یک Mutation است!
با اینکه اشیاء در جاوااسکریپت ماهیت تغییرپذیر دارند، اما در استیت React باید با آنها دقیقاً مانند اعداد و رشتهها (یعنی تغییرناپذیر) رفتار کنید. به جای دستکاری مستقیم، باید کل شیء را با یک نسخه جدید جایگزین کنید.
اگر متغیری را مستقیماً دستکاری کنید، ظاهر صفحه بهروزرسانی نخواهد شد. به این بخش از کد یک کامپوننت که قرار است نقطه قرمزی را با حرکت موس جابجا کند نگاه کنید:
// کدی که کار نمیکند!
onPointerMove={e => {
position.x = e.clientX;
position.y = e.clientY;
}}
این کد شیء رندر قبلی را دستکاری میکند. اما از آنجا که تابع اصلاحکننده استیت (setPosition) صدا زده نشده، React اصلاً روحش هم خبردار نمیشود که دیتا تغییر کرده است؛ در نتیجه هیچ واکنشی نشان نمیدهد.
برای رفع این مشکل و کلید زدن رندر مجدد، باید یک شیء جدید بسازید و آن را به setPosition پاس دهید:
// روش درست و اصولی
onPointerMove={e => {
setPosition({
x: e.clientX,
y: e.clientY
});
}}
با این کار به React اعلام میکنید: «شیء position را با این اسنپشات جدید تعویض کن و کامپوننت را دوباره رندر کن.»
🔎 چه زمانی Mutation مشکلی ندارد؟ > دستکاری مستقیم اشیاء فقط زمانی جرم است که آن شیء از قبل در استیت ریاکت جا خوش کرده باشد. اگر یک شیء کاملاً جدید را همین الان درون تابع ساختهاید، دستکاری آن کاملاً بیخطر است، چون هنوز هیچ بخش دیگری از برنامه به آن ارجاع (Reference) ندارد:
const nextPosition = {}; nextPosition.x = e.clientX; // کپی محلی کاملاً مجاز است (Local Mutation) setPosition(nextPosition);
...)در مثال بالا، شیء همیشه از نو و با دیتای کاملاً جدید ساخته میشد. اما در بیشتر مواقع (مثل فیلدهای یک فرم بزرگ)، شما میخواهید فقط یک فیلد را آپدیت کنید و بقیه اطلاعات قبلی شیء بدون تغییر باقی بمانند.
برای اینکه مجبور نباشید تکتک ویژگیهای قبلی را دستی بنویسید، از نحو Spread Operator (...) استفاده کنید:
import { useState } from 'react';
export default function Form() {
const [person, setPerson] = useState({
firstName: 'Barbara',
lastName: 'Hepworth',
email: 'bhepworth@sculpture.com'
});
function handleFirstNameChange(e) {
setPerson({
...person, // ۱. یک کپی از تمام فیلدهای قدیمی بگیر
firstName: e.target.value // ۲. فیلد مشخص شده را بازنویسی (Override) کن
});
}
// بقیه متدها هم به همین ترتیب عمل میکنند...
}
اپراتور ... ویژگیهای شیء را فقط یک لایه عمیق کپی میکند. این ویژگی باعث سرعت بسیار بالای آن میشود، اما اگر شیء شما ساختار تودرتو داشته باشد، برای آپدیت لایههای عمیقتر باید بیشتر از یکبار از ... استفاده کنید.
ساختار تودرتوی زیر را در نظر بگیرید:
const [person, setPerson] = useState({
name: 'Niki de Saint Phalle',
artwork: {
title: 'Blue Nana',
city: 'Hamburg',
}
});
اگر بخواهید person.artwork.city را به صورت اصولی و بدون شکستن قانون تغییرناپذیری (Immutability) ویرایش کنید، باید لایه به لایه کپی کنید. یعنی ابتدا شیء داخلی artwork را کپی کرده و مقدار جدید را بنویسید، سپس شیء اصلی person را کپی کرده و به artwork جدید اشاره کنید:
setPerson({
...person, // کپی لایه اول (نام و ارتورک قدیمی)
artwork: {
...person.artwork, // کپی لایه دوم (عنوان و تصویر قدیمی)
city: 'New Delhi' // بازنویسی فیلد نهایی در عمق شیء
}
});
🧠 واقعیت فنی: اشیاء در حقیقت تودرتو نیستند! > شاید در کدنویسی اشیاء را داخل هم بنویسید، اما در حافظه کامپیوتر چیزی به نام شیء تودرتو وجود ندارد. در واقع شما با دو شیء مجزا طرف هستید که یکی از آنها با یک کلید به دیگری اشاره (Point) میکند. اگر چندین شیء به یک آدرس مشترک اشاره کنند و شما آن آدرس را مستقیماً دستکاری کنید، دیتای هر دو بخش ناخواسته خراب میشود. برای همین کپی لایه به لایه در React حیاتی است.
اگر استیتهای شما ساختار بسیار عمیق و پیچیدهای دارند، کپی کردن مداوم با ... کد شما را شلوغ و تکراری میکند. کتابخانه محبوب Immer به شما اجازه میدهد تا با همان ساختار ساده و مستقیمِ دستکاری (mutation) کد بنویسید، در حالی که در پشت صحنه خودش وظیفه کپی ایمن لایهها را بر عهده میگیرد.
با استفاده از هوک useImmer به جای useState کدهای شما به این شکل ساده میشوند:
import { useImmer } from 'use-immer';
export default function Form() {
const [person, updatePerson] = useImmer({
name: 'Niki de Saint Phalle',
artwork: {
title: 'Blue Nana',
city: 'Hamburg',
}
});
function handleCityChange(e) {
// پارامتر draft یک نسخه نیابتی (Proxy) تحت کنترل Immer است
updatePerson(draft => {
draft.artwork.city = e.target.value; // ظاهر کد شبیه Mutation است اما کاملاً ایمن است!
});
}
// ...
}
پارامتر draft که Immer در اختیار شما قرار میدهد، یک شیء ویژه از نوع Proxy است که تمام رفتارهای شما را ضبط میکند. Immer متوجه میشود که شما کدام بخش از این پیشنویس را تغییر دادهاید و در پایان، خودش یک شیء کاملاً جدید و اصلاحشده تولید میکند، بدون اینکه استیتهای رندرهای گذشته دستخوش تغییر شوند.
با تمام اشیاء موجود در استیت React به عنوان دیتای فقط خواندنی (Immutable) رفتار کنید.
دستکاری مستقیم فیلدهای یک شیء در استیت، هیچ رندری ایجاد نکرده و اسنپشاتهای رندرهای قبلی را هم خراب میکند.
برای بهروزرسانی آسان اشیاء یکلایهای، از نحو Spread یعنی {...obj, field: 'value'} استفاده کنید.
نحو Spread سطحی است؛ برای آپدیت اشیاء تودرتو، باید کپی کردن را از عمیقترین نقطه تا بالاترین سطح زنجیره تکرار کنید.
برای تمیز نگهداشتن کدهای استیتهای تودرتو و جلوگیری از بازنویسیهای مکرر، از Immer کمک بگیرید.
این محتوا کاملا رایگان توسط تیم کدلپر ترجمه شده و در اختیار شما کاربران عزیز قرار گرفته است، هر گونه کپی برداری برای مقاصد غیر رایگان و بدون ذکر منبع، مورد پیگیری قانونی قرار میگیرد.
ترجمه شده از منبع: https://react.dev/learn