چرا super(props) رو مینویسیم؟
۹ آذر ۱۳۹۷ • ☕️ 6 min read
Translated by readers into: فارسی
شنیدم که Hooks موضوع داغه روزه جدیدن. شاید باورتون نشه ولی میخوام که این وبلاگو با توضیح دادن یه سری از چیزای باحال class کامپوننتها شروع کنم. چطوره؟!
این چیزایی که اینجا مینویسم چیزایی نیستن که شما باهاش بتونید ریاکت رو بهتر بنویسید. ولی اگه دوست دارید که در مورد ریاکت عمیقتر بدونید که چیزای مختلف چجوری کار میکنه ممکنه براتون جالب باشه.
خب بریم سراغ اولین پست.
من توی زندگیم خیلی بیشتر از چیزی که دونسته باشم super(props)
نوشتم:
class Checkbox extends React.Component {
constructor(props) {
super(props); this.state = { isOn: true };
}
// ...
}
قطعن که پروپوزال class fields بهمون این اجازه رو میده که درگیر این داستان نشیم:
class Checkbox extends React.Component {
state = { isOn: true };
// ...
}
سینتکسی شبیه به این برنامهریزی شده بود وقتی که ریاکت ورژن 0.13 پشتیبانی از کلاسها رو اضافه کرد. تعریف کردن constructor
و صدا زدن super(props)
همیشه یک راه موقت در نظر گرفته شده بود تا زمانی که class fields یک راه مشابه بهتری رو ارائه رو بده خودش.
ولی بذارید برگردیم به مثالمون و فقط از خود ES2015 استفاده کنیم:
class Checkbox extends React.Component {
constructor(props) {
super(props); this.state = { isOn: true };
}
// ...
}
چرا ما super
رو صدا میزنیم؟ آیا میتونیم که صداش نزنیم؟ اگه که باید صداش بزنیم چی میشه اگه props
رو پاس ندیم بهش؟ آیا پارامترهای دیگهای هم هست؟
توی جاوااسکریپت super
به کلاس پدرش (کلاسی که از اون داره ارث میبره) اشاره میکنه. (توی مثال ما super داره به React.Component
اشاره میکنه.)
و یک نکتهی مهمی که هست اینه که شما نمیتونی از this
توی یک constructor
استفاده کنی تا قبل اینکه constructor کلاس پدرش رو صدا نزده باشی. یعنی جاوااسکریپت بهتون این اجازه رو نمیده:
class Checkbox extends React.Component {
constructor(props) {
// 🔴 Can’t use `this` yet
super(props);
// ✅ Now it’s okay though
this.state = { isOn: true };
}
// ...
}
البته دلایل خوبی پشت این قضیس که جاوااسکریپت مجبورتون میکنه که constructor کلاس پدر باید قبل اینکه از this
استفاده کنید اجرا بشه. این کلاسها رو در نظر بگیرید:
class Person {
constructor(name) {
this.name = name;
}
}
class PolitePerson extends Person {
constructor(name) {
this.greetColleagues(); // 🔴 This is disallowed, read below why
super(name);
}
greetColleagues() {
alert('Good morning folks!');
}
}
تصور کنید که میتونستید از this
استفاده کنید. یک ماه بعد میومدیم که greetCollegues
رو یکم تغییر میدادیم و اسم شخص رو هم توی متن پیام اضافه میکردیم:
greetColleagues() {
alert('Good morning folks!');
alert('My name is ' + this.name + ', nice to meet you!');
}
ولی فراموش کردیم که this.greetColleagues()
داره قبل اینکه super()
بخواد this.name
رو ست کنه صدا زده میشه. بنابراین اصلن this.name
هنوز تعریف نشده! همونطور که میبینید کدای اینطوری فکر کردن بهشون میتونه خیلی سخت باشه.
برای جلوگیری از مشکلات و داستانهای اینطوری جاوااسکریپت مجبورتون میکنه که اگه از this
میخواید توی constructor استفاده کنید باید super
رو اول از همه صدا بزنید. بذارید که پدر کارشو بکنه! و این محدودیت هم توی ریاکت و کامپوننتهای که با کلاس مینویسید هستش.
constructor(props) {
super(props);
// ✅ Okay to use `this` now
this.state = { isOn: true };
}
یک سوال دیگهای که به وجود میاد: چرا props
رو پاس میدیم؟
ممکنه که فکر کنید که پاس دادن props
به super
لازمه که constructor React.Component
بتونه this.props
رو مقداردهی اولیه کنه.
// Inside React
class Component {
constructor(props) {
this.props = props;
// ...
}
}
که خب خیلی هم بیراه نیست. در اصل این طوری هستش که انجام میده
ولی یجورایی اگه حتی شما super()
رو بدون پارامتر props
هم صدا بزنید میبینید که توی render
هنوز به this.props
دسترسی دارید. (اگه باورتون نمیشه خودتون امتحان کنید!)
چطوری این مدلی کار میکنه؟ به این برمیگرده که ریاکت props
رو هم به instance اضافه میکنه بعد از اینکه constructorـتون رو صدا میزنید.
// Inside React
const instance = new YourComponent(props);
instance.props = props;
پس حتی وقتی که شما فراموش کردید که props
رو به super()
پاس بدید، ریاکت خودش بعدش ستش میکنه. یک دلیلی هم برای این کار هست.
وقتی که ریاکت پشتیبانی از کلاسها رو اضافه کرد، فقط پشتیبانی از خود کلاسهای ES6 نبود. هدف این بود که یک بخش زیادی از abstractionهای کلاسها هم تا جایی که ممکن بود پشتیبانی کنه. برای تعریف کردن کامپوننت مشخص نبود که چقدر این قابل قبول خواهد بود توی ClojureScript, CoffeeScript, ES6, Fable, Scala.js, TypeScript یا چیزای دیگه. بنابراین ریاکت برای صدا زدن super()
(که توی کلاسهای ES6 اجباریه) هیچ اجباری نداشت.
خب پس همین کافیه که بنویسیم super()
به جای اینکه بنویسیم super(props)
؟
شاید به خاطر اینکه گیجکنندس، نه. درسته که خود ریاکت this.props
رو ست میکنه بعد این که constructorـتون اجرا میشه. ولی خب this.props
از جایی که super
تا انتهای constructorـتون undefined خواهد بود:
// Inside React
class Component {
constructor(props) {
this.props = props;
// ...
}
}
// Inside your code
class Button extends React.Component {
constructor(props) {
super(); // 😬 We forgot to pass props
console.log(props); // ✅ {}
console.log(this.props); // 😬 undefined }
// ...
}
حتی میتونه چالشهای بیشتری رو داشته باشه برای ایرادیابی و دیباگ کردن اگه که این اتفاق توی متدهایی بیفته که دارن از constructor صدا زده میشن. و به همین خاطر هستش که توصیه میکنم که همیشه super(props)
استفاده کنید حتی اگه جاهایی که فکر میکنید نیاز به این کار نیست خیلی:
class Button extends React.Component {
constructor(props) {
super(props); // ✅ We passed props
console.log(props); // ✅ {}
console.log(this.props); // ✅ {}
}
// ...
}
اینطوری مطمئن میشیم که this.props
ست شده حتی قبل اینکه constructorـیی وجود داشته باشه.
آخرین چیزی که میخوام بگم چیزیه که کاربرای ریاکت از خیلی وقت پیش ممکنه در موردش کنجکاو باشن.
ممکنه وقتی از Context API توی کلاسها استقاده میکنید (چه نسخهی contextTypes
قدیمی چه مدرنش که توی ورژن ۱۶.۶ اضافه شده) توجه کرده باشید، context
به عنوان پارامتر دوم پاس داده میشه به constructor.
خب چرا ما نباید جاش super(props, context)
بنویسیم؟ می تونیم این کارم بکنیم ولی context اغلب کمتر استفاده میشه و این داستانایی که پیش میاد خیلی به چشم نمیاد برای context.
با پروپوزال class fields کل این مشکلات و داستانا از بین میرن در هر صورت. بدون نوشتن constructor، همهی پارامترها خودکار ست میشن. این چیزیه که به اکسپرشنی مثل state = {}
اجازه این رو میده که this.props
یا this.context
به درستی رفرنس بدن اگه نیاز شد.
با Hooks ما حتی به super
و this
نیاز نداریم به کل. ولی این یک بحث دیگس که بعدن بهش میپردازیم.