Referential Equality: چرا دو چیز که یکسان بهنظر میرسند، برابر نیستند
در درس قبل دیدیم که React.memo با Objectها، Arrayها و توابع تازهساختهشده، عملاً بیاثر است. دلیل این رفتار، مفهومی است به نام Referential Equality؛ مفهومی که در فصلهای قبل چند بار از کنارش رد شدیم (در Dependency Array و در Immutability)، اما اینجا اولین باری است که مستقیماً و کامل به آن میپردازیم.
برابری مقدار در برابر برابری مرجع
در جاوااسکریپت، مقادیر اولیه (رشته، عدد، Boolean) بر اساس مقدارشان مقایسه میشوند. اما Objectها، Arrayها و توابع، بر اساس مرجع (Reference) مقایسه میشوند؛ یعنی حتی اگر محتوایشان کاملاً یکسان باشد، دو Object جداگانه، با عملگر ===، برابر شناخته نمیشوند:
console.log(5 === 5); // true
console.log({ a: 1 } === { a: 1 }); // false
console.log([1, 2] === [1, 2]); // false
function makeFn() {
return () => {};
}
console.log(makeFn() === makeFn()); // false
دو Object با محتوای دقیقاً یکسان، دو آدرس حافظه متفاوت دارند؛ و === فقط همین آدرسها را مقایسه میکند، نه محتوای داخلشان.
چرا این نکته در React اهمیت زیادی دارد
React در چند جای کلیدی، برای تشخیص تغییر، دقیقاً از همین مقایسه مرجع استفاده میکند: Dependency Array در useEffect، مقایسه Props در React.memo، و تشخیص تغییر State. هر جایی که یک Object یا Array، مستقیماً داخل بدنه کامپوننت (بدون هیچ ابزار کمکی) تعریف شود، در هر Render، یک مرجع تازه دارد:
function ProductCard() {
const style = { color: "red" }; // هر Render، یک Object جدید
return <div style={style}>...</div>;
}
حتی اگر محتوای style همیشه { color: "red" } باشد، از نظر React، این هر بار یک مقدار «جدید» است؛ چون مرجعش با رندر قبلی فرق دارد.
ارتباط با Dependency Array
همانطور که در فصل ششم دیدیم، اگر یک Object تازهساختهشده به Dependency Array یک Effect بدهید، آن Effect با هر Render دوباره اجرا میشود؛ چون React با مقایسه مرجع، همیشه تشخیص میدهد این مقدار «تغییر کرده».
ارتباط با React.memo
دقیقاً همین رفتار بود که در درس قبل دیدیم: یک Prop از نوع Object یا تابع که در هر Render والد از نو ساخته میشود، باعث میشود React.memo، با وجود مقایسه سطحی، همیشه نتیجه بگیرد Props تغییر کرده و کامپوننت را دوباره Render کند.
راهحل کلی: پایدار نگهداشتن مرجع
برای اینکه یک Object، Array یا تابع بین Renderهای مختلف، همان مرجع قبلی را حفظ کند (مگر اینکه واقعاً چیزی در آن عوض شده باشد)، باید آن را با ابزاری مثل useMemo یا useCallback بسازیم؛ ابزارهایی که دقیقاً برای همین منظور طراحی شدهاند و در دو درس بعدی با جزئیات کامل بررسی میشوند.
مثال قابل اجرا
در این مثال، با هر کلیک، یک Object جدید (با همان محتوا) ساخته میشود و مقایسه === آن با نسخه قبلی، در کنسول، همیشه false چاپ میشود.
جمعبندی
در جاوااسکریپت، Objectها، Arrayها و توابع بر اساس مرجع مقایسه میشوند، نه محتوا؛ و هر بار که این مقادیر مستقیماً در بدنه کامپوننت ساخته شوند، مرجعی تازه دارند، حتی اگر محتوایشان عوض نشده باشد. این رفتار، ریشه اصلی محدودیتهایی است که در Dependency Array و React.memo دیدیم. در درس بعدی، با useMemo، اولین ابزار برای حل این مشکل آشنا میشویم.
