یه نکتهی جالب راجع به arrow functionها در کلاس کامپوننتهای ریاکت
مقدمه
همهی ما از arrow functionهای جاوااسکریپت استفاده میکنیم و میدونیم که مقدار this داخل arrow functionها برابر مقدار this بیرون از اونهاست (چون arrow functionها this binding انجام نمیدن)!
با این حساب اگر از arrow functionها به عنوان متد یک آبجکت استفاده کنیم، مقدار this داخل این متد به خود آبجکت اشاره نمیکنه:
اگر کد بالا رو اجرا کنیم، میبینیم که this به آبجکت گلوبال اشاره میکنه (و نه به آبجکت obj).
کاملا منطقیه. نه؟
اما صبر کنید. چرا وقتی از arrow functionها به عنوان فیلد یک class استفاده میکنیم، مقدار this به خود آبجکتهای اون کلاس اشاره میکنه؟! (دقیقا برعکس رفتاری که در کد بالا دیدیم)
برای مثال توی کد زیر که یه کلاس کامپوننت ریاکت هست، متد handleClick رو به صورت arrow function تعریف کردهایم، اما مقدار this به خود آبجکت react component اشاره میکنه:
We Need To Go Deeper
برای اینکه مقدار this داخل فیلدهای کلاس که با arrow function تعریف میکنیم، به خود آبجکت کلاس اشاره کنه، دو مکانیزم مهم جاوااسکریپت یعنی Closure و This keyword با هم همکاری میکنند.
پس قبل از هر چیز این دو مفهوم رو خیلی کوتاه بررسی میکنیم:
This Keyword
کلمه کلیدی this مثل یه پارامتر implicit هست که به هر تابع پاس داده میشه.
مقدار this داخل هر تابع فقط با توجه به اینکه این تابع کجا و چطور صدا شده مشخص میشه.
همینطور که میبینید یه تابع رو با روشهای مختلف صدا کردیم، و هر بار با توجه به نحوهی صدا شدن تابع، مقدار this تغییر کرد.
به عنوان مثال وقتی یک تابع را با new صدا میکنیم (constructor)، یک آبجکت جدید ساخته میشه که this به این آبجکت جدید اشاره خواهد کرد (خط 9).
از طرفی arrow functionها this ندارند. یعنی مقدار this داخل یه arrow function مثل یه متغیر معمولی تعیین میشه. به این صورت که انجین جاوااسکریپت اول داخل خود تابع دنبال identifierی به اسم this میگرده و وقتی پیداش نکرد، مقدار this توی تابع بیرونی رو پیدا میکنه (در نتیجه مقدار this داخل arrow functionها برابر مقدار this بیرون از اونهاست).
Closure
هر بار که یه تابع رو صدا میکنیم، متغیرهایی که توی هر تابع تعریف کردهایم، داخل memory ساخته میشن (انجین جاوااسکریپت این متغیرها رو داخل ساختار دادهای به اسم Lexical Environment نگهداری میکنه).
و طبیعتا بعد از اینکه اجرای تابع تموم شد، انجین جاوااسکریپت میتونه این lexical environment رو از memory پاک کنه. مگر اینکه...!
کد زیر رو در نظر بگیرید:
همینطوری که میبینید توی خط 7، تابع createMultiplier رو صدا میکنیم و خروجی این تابع (یعنی تابع inner) رو داخل متغیر double میریزیم.
اما به خط 8 و 9 دقت کنید. وقتی تابع double رو اجرا میکنیم، اجرا شدن تابع createMultiplier تموم شده. ولی همچنان تابع inner به متغیر a که در تابع بیرونی تعریف شده دسترسی داره.
در واقع انجین جاوااسکریپت متوجه شد که همچنان یه reference به تابع داخلی داریم، پس lexical environment تابع createMultiplier رو هم از memory پاک نکرد تا همچنان به متغیر a دسترسی داشته باشیم.
خلاصه: به لطف closure، توابع جاوااسکریپت به متغیرهایی که در scope بیرونی خودشون تعریف شده دسترسی دارند. حتی وقتی بیرون از این scope اجرا میشن.
مفهوم Closure توی بقیهی زبانهای محبوب مثل Python و Ruby و ... هم وجود داره.
همکاری Closure و This در کلاس کامپوننت ریاکت
حالا که دو مفهوم Closure و This رو مرور کردیم، بیاید برگردیم به سوال اصلی:
چرا وقتی متدهای یه کلاس کامپوننت ریاکت رو به صورت arrow function تعریف میکنیم، مقدار this به خود آبجکت کلاس اشاره میکنه؟
برای جواب دادن به این سوال، بیاید ببینیم فیلدهای کلاس، بعد از transpile شدن چه شکلی میشن:
همینطور که میبینید، فیلدهای کلاس به داخل constructor منتقل شدند. حتی فیلد handleClick که با arrow function تعریف کردیم.
از این کد واضحه که متدهای کامپوننت که با arrow functionها تعریف میکنیم، در واقع به داخل constructor منتقل میشن.
همچنین گفتیم که arrow functionها this ندارند. پس مقدار this داخل این تابعها دقیقا برابر مقدار this داخل constructorها میشه! از طرفی میدونیم که مقدار this داخل constructorها به آبجکت جدیدی که ساخته شده اشاره میکنه.
شاید بپرسید زمانی که ما متد handleClick رو صدا میکنیم، اجرا شدن constructor تموم شده. پس چطور همچنان arrow function به مقدار this داخل constructor دسترسی داره؟ برای فهمیدن این موضوع یه بار دیگه قسمت closure رو بخونید :)
به این ترتیب هر زمانی که این arrow function رو صدا کنیم، مقدار this داخل اون به خود آبجکت ساخته شده توسط کلاس اشاره میکنه.
نکتهی مهم دیگه اینکه وقتی متدهای یه کامپوننت رو به صورت arrow function و داخل یه فیلد کلاس تعریف میکنیم، در واقع داریم داخل تک تک instanceهای این کامپوننت این تابع رو تعریف میکنیم (یعنی برعکس متدهای معمولی که فقط داخل prototype تعریف میشن).
البته به لطف انجینهای جاوااسکریپت مدرن، احتمالا این موضوع مشکلات performanceی قابل لمسی ایجاد نمیکنه. مگر اینکه بخواهیم تعداد خیلی خیلی زیادی instance از یه کامپوننت بسازیم.
مطلبی دیگر از این انتشارات
ESLint برای React Native
مطلبی دیگر از این انتشارات
خطای کمبود فضای حافظه در هنگام Build پروژه های بزرگ React
مطلبی دیگر از این انتشارات
الگوهای ری اکت - قسمت ۱