برنامه‌نویسی

کار با hookها در React

کار با 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ها می‌توانیم اجزای برنامه را به‌طور قابل توجهی ساده کنیم اما باید در هنگام استفاده از آن‌ها به مواردی نیز توجه داشته باشیم. به این صورت با دنبال کردن دستورالعمل‌ها، باگ‌های کمتری در برنامه‌ی شما ایجاد می‌شود و درک برنامه برای سایر توسعه‌دهندگان نیز راحت‌تر می‌شود.

منبع: https://tsh.io/blog/react-hooks-best-practices