کار با hookها در React
۱۴ آبان ۱۳۹۹
hookها بهتازگی بخشی از برنامههای React شدهاند اما در حال حاضر توسعهدهندگان با مشکلهای مختلفی در مدیریت stateها و موارد دیگر روبرو هستند. استفاده از تمام مزیتهای hookها در React نیاز به کسب تجربه دارد، حال ما در این مقاله سعی داریم تا روشهای مختلفی که طی سالها به دست آمده را در اختیار شما قرار دهیم.
hookها در ابتدای سال ۲۰۱۹ به React اضافه شدند و به سرعت میان توسعهدهندگان محبوبیت پیدا کردند. به لطف آنها میتوان بدون classهای JavaScript و extend کردن React.Component
، کامپوننتهای stateful ایجاد کرد. همچنین کدهای ما با استفاده از hookها، سادهتر، خلاصهتر و خواناتر میشوند.
بدین صورت با خلاص شدن از classها، بسیاری از کدهای اضافی حذف میشوند، مثلا دیگر نیازی به استفاده از constructor، متد super و propsها نیست. حتی نیازی به bind کردن متدها یا فکر کردن به اینکه this
میتواند چه باشد هم نیست.
هنگام استفاده از hookها باید چندین مورد را بهخاطر بسپارید، بدین صورت کدهای شما مطابق انتظار عمل میکنند و برای دیگران هم قابل درک میشوند. حال در این مقاله به useState
و useEffect
خواهیم پرداخت.
اصول اولیه
امیدوارم این مورد را از قبل بدانید اما لازم به ذکر است که hookها، conditional نیستند یعنی نمیتوانید از آنها درون loopها یا nested functionهای درون کامپوننت استفاده کنید. بنابراین اگر بخواهید از hookها به صورت شرطی (conditional) استفاده کنید، باید چه کرد؟ میتوانید شروط یا conditionها را درون hook تعریف کنید.
چرا این موضوع اینقدر مهم است؟ hookها را میتوان چندین بار در یک کامپوننت مشخص فراخوانی کرد و عملیات در React بر اساس ترتیب فراخوانی آنها انجام میشوند. بدین صورت با عدم رعایت این موارد، اشکالهای جدی و خطاهای مهلکی در برنامه شما رخ میدهد.
خوشبختانه افزونهای به نام eslint-plugin-react-hooks توسط تیم توسعه React ساخته شده که شما را مجبور به پیروی از این قوانین میکند. اگر از Create React App برای ساخت برنامههای خود استفاده میکنید، این افزونه بهطور پیشفرض نصب شده است.
این افزونه نه تنها با hookهای React بلکه با تمام hookهای شخصیسازی شده کار خواهد کرد و برای این کار باید از یک قرارداد پیروی کنید، آنهم این است که در شروع نام hookهای خود از use استفاده کنید.
مشکلهای استفاده از useState در React
همانطور که قبلا اشاره کردیم، هنگام استفاده از hookها حتی به طور کلیتر در برنامه بهتر است تا به قراردادهای موجود در برنامهنویسی پایبند باشیم. فرض بر این است که المنتها فراخوانی شدهاند و میخواهیم مقادیری که از طریق useState
دریافت میکنیم را نامگذاری کنیم:
const [someState, setSomeState] = useState(0);
با این کار، شما قادر خواهید بود توابع مرتبط با stateهایی که update شدهاند را به راحتی پیدا کنید و بهطور کلیتر کدهای شما با برنامه سازگار باشد.
استفاده از useState
یکی از مناسبترین راهها برای ذخیرهسازی stateهایی با مقادیر boolean یا دادههای ورودی فرم هستند. برخلاف stateها در classها، شما به یک آبجکت محدود نیستید و با useState میتوانید valueهای جداگانهای را برای هر مورد ایجاد کنید.
برای مثال هنگام کار با فیلدهای موجود در فرم، بهجای نگه داشتن اطلاعات کاربر در یک state مانند مثال زیر:
const [user, setUser] = useState(
{ name: 'John', email: 'john@example.com', age: 25 }
);
بهتر است برای هر فیلد موردی جداگانه ایجاد کنید:
const [name, setName] = useState('John');
const [email, setEmail] = useState('john@example.com');
const [age, setAge] = useState(25);
با مثال بالا باید کاملا متوجه شده باشید که محدودیتی وجود ندارد و با استفاده از useState
میتوانید آبجکتها را ذخیره کنید اما باید به یک مورد دقت داشته باشید. در کامپوننتهای مبتنی بر class، زمانیکه با استفاده از this.setState
یک state را update میکنید، مقادیر جدید با مقادیر قدیمی ادغام (merge) میشوند اما با استفاده از useState
، مقادیر state قبلی با مقادیر جدید جایگزین میشوند. برای اینکه این موضوع را بهتر متوجه شوید به مثال زیر دقت کنید:
const [user, setUser] = useState(
{ name: 'John', email: 'john@example.com', age: 25 }
);
setUser({ name: 'Harry' });
// result { name: 'Harry' }
اگر stateهای خود را مانند مثال بالا، update کنید، تمام دادههای قبلی خود را از دست میدهید اما نگران نباشید، راهی برای کنترل این موضوع وجود دارد. مانند this.setState
میتوانید دادههای قبلی خود را با استفاده از تابع prevState
دریافت کنید حال برای این کار به مثال زیر دقت کنید:
const [user, setUser] = useState(
{ name: 'John', email: 'john@example.com', age: 25 }
);
setUser((prevState) => {
return Object.assign({}, prevState, { name: 'Harry' });
});
// result { name: 'Harry', email: 'john@example.com', age: 25 }
یا حتی میتوانید از اپراتور spread استفاده کنید:
const [user, setUser] = useState(
{ name: 'John', email: 'john@example.com', age: 25 }
);
setUser((prevState) => ({ ...prevState, name: 'Harry' }));
// result { name: 'Harry', email: 'john@example.com', age: 25 }
البته در اینجا باید به یک نکته مهم توجه کنید. زمانی که دادهها را با استفاده prevState
، بهروزرسانی میکنید نباید مانند مثال زیر عمل کنید:
const [isOpen, setIsOpen] = useState(false);
// ...
setIsOpen(!isOpen);
متوجه مشکل شدهاید؟ بهروزرسانی stateها به صورت asynchronous یا ناهمزمان انجام میشوند، بنابراین هرگز نباید به این صورت از مقادیر قبلی استفاده کنید و مثال زیر راه حلی برای این مشکل است:
const [isOpen, setIsOpen] = useState(false);
// ...
setIsOpen(prevIsOpen => !prevIsOpen);
استفاده از useEffect
useEffect
یکی از hookهای React است که امکان تفکیک منطقی توابع درون کامپوننت براساس آنچه که قرار است انجام دهند و نه بر اساس زمان رخداد را به ارمغان میآورد. ما در classها مجبور بودیم تا توابع غیرمرتبط را در lifecycle methodها نگهداریم اما حال با استفاده از useEffect
میتوانیم کدهای خود را به قطعههای کوچکتری تقسیم کنیم و مانند useState
نگران فراخوانی چندین باره این hook برای هر مورد نخواهیم بود.
اکثر اوقات هنگام کار با useEffect
میبایست از برخی توابع آماده زبان JavaScript استفاده کنید. حال سوال پیش میآید که چرا؟ بگذارید فرض کنیم که شما میخواهید یک تایمر در برنامه خود ایجاد کنید و به همین دلیل از تابع setInterval
درون useEffect
استفاده میکنید تا مقادیر را هر ۱۰۰۰ میلیثانیه، یک بار بهروزرسانی کند.
const [timer, setTimer] = useState(0);
useEffect(() => {
setInterval(() => {
setTimer((prevTime) => prevTime + 1);
}, 1000);
});
بنابراین بهطور پیشفرض در هر بار رندر صفحه، useEffect
اجرا میشود. پس زمانی که شما دوباره stateهای کامپوننت را از درون useEffect
بهروزرسانی کنید، باعث رندر مجدد میشوید. همه اینها یعنی شما در یک حلقه تکراری بدون پایان میافتید یا حداقل تا زمانی که مرورگر یا API شما کِرَش کند، این تکرارها یکی پس از دیگری اجرا میشوند. خوشبختانه تیم توسعه React به این موضوع فکر کرده است و یکی از قوانین موجود در eslint-plugin-react-hooks با نام exhaustive-deps این موضوع را ثابت میکند.
اما در این صورت تایمر هم کمکی نخواهد کرد زیرا پس از بهروزرسانی متغیر تایمر، بارها و بارها setInterval
اجرا میشود. البته شما میتوانید از setTimeout
استفاده کنید اما دو روش دیگر وجود دارد که میتوان با useEffect
به کمک یک array خالی یا استفاده از توابع cleanup، تایمر دلخواه را پیادهسازی کرد.
حال React با قراردادن یک array خالی به عنوان وابستگی فرض میکند که effect شما به هیچ مقداری وابسته نیست و دوباره آن را اجرا نمیکند:
const [timer, setTimer] = useState(0);
useEffect(() => {
setInterval(() => {
setTimer((prevTime) => prevTime + 1);
}, 1000);
}, []);
کار دیگری که میتوانید انجام دهید استفاده از یک تابع cleanup است که عملکردی مشابه componentWillUnmount
در کامپوننتهای مبتنی بر class دارد. تمام دادههایی که از تابع به useEffect پاس داده میشوند، در هنگام حذف کامپوننت فراخوانی میشود. یا اگر کامپوننت چندین بار مانند این مثال رندر شود، آخرین effect قبل از اجرای دوباره پاک میشود. در اینجا میتوانید clearInterval
را با آیدی setInterval
، return کنید.
const [timer, setTimer] = useState(0);
useEffect(() => {
const id = setInterval(() => {
setTimer((prevTime) => prevTime + 1);
}, 1000);
return () => clearInterval(id);
}, [timer]);
جمعبندی
این مقاله برای آموزش استفاده از hookها در React بسیار کوتاه است و موارد بیشتری وجود دارند که شما میتوانید آنها را یاد بگیرید، بههرحال امیدواریم در این مقاله نکتههای مفیدی درباره useState
و useEffect
یاد گرفته باشید.
همانطور که مشاهده کردید، با استفاده از hookها میتوانیم اجزای برنامه را بهطور قابل توجهی ساده کنیم اما باید در هنگام استفاده از آنها به مواردی نیز توجه داشته باشیم. به این صورت با دنبال کردن دستورالعملها، باگهای کمتری در برنامهی شما ایجاد میشود و درک برنامه برای سایر توسعهدهندگان نیز راحتتر میشود.