React بدون شک محبوب‌ترین کتابخانه جاوااسکریپت است. پیشرفت‌هایی که اخیراً تیم React انجام داده است فقط بر توسعه‌دهندگان تأثیر نمی‌گذارد، بلکه کسانی که از برنامه‌های ساخته شده با React استفاده می‌کنند نیز تحت تاثیر این تغییرات قرار می‌گیرند. در این مقاله قصد داریم تا در مورد برخی از best practiceها در توسعه برنامه‌های React صحبت کنیم.

تغییرناپذیری State در React

آیا تاکنون به این موضوع فکر کرده‌ایم که چرا React تاکید بسیار زیادی در مورد تغییرناپذیری داده‌ها دارد؟ ممکن است به عنوان یک برنامه نویس تازه‌کار فکر کنیم که تغییرات در داده‌ها در جاوااسکریپت کاملاً طبیعی هستند. در نهایت، ما به راحتی ویژگی‌هایی را به آبجکت‌ها اضافه یا از آن‌ها حذف می‌نماییم و آرایه‌ها را دستکاری می‌کنیم.

اما در React، اصل immutability یا تغییرناپذیری به معنای این نیست که هرگز نباید state را تغییر دهیم، بلکه هدف این است که ثبات را حفظ نماییم.

زمانی که state را به طور مستقیم تغییر می‌دهیم، React نمی‌تواند تغییرات را به طور قابل اعتماد شناسایی کند. این به این معناست که رابط کاربری ما ممکن است به درستی به‌روزرسانی نشود. راه حلی که برای این مشکل وجود دارد این است که داده‌های قدیمی را با نسخه‌های جدید آن‌ها جایگزین نماییم.

به عنوان مثال، اگر نیاز داریم تا یک کاربر جدید اضافه کنیم، به جای این که کاربر جدید را مستقیما به آرایه موجود push کنیم، باید یک آرایه جدید با کاربر جدید بسازیم.

const updatedUsers = [...users, newUser];

کد بالا از عملگر spread برای ایجاد یک آرایه جدید به نام updatedUsers استفاده می‌کند که users موجود را با newUser ترکیب می‌نماید.

این رویکرد با تغییر ندادن آرایه users اصلی، تغییرناپذیری را در React حفظ می‌کند. در عوض، یک نمایش state جدید ایجاد می‌کند و به React اجازه می‌دهد تا رندرینگ را بهینه کرده و تغییرات state قابل پیش‌بینی را تضمین نماید. هنگامی که state را با استفاده از setUsers(updatedUsers); به‌روزرسانی می‌کنیم، React کامپوننت را بر اساس این آرایه جدید، با رعایت best practiceها برای مدیریت state، دوباره رندر می‌کند.

این کار اطمینان حاصل می‌کند که React تغییر را تشخیص داده و کامپوننت ما را مجدداً رندر می‌نماید.

عدم استفاده از useState برای هر مورد

نکته‌ای که باید به آن توجه کنیم این است که لازم نیست همه چیز را در state ذخیره کنیم. state ابزار قدرتمندی است، اما استفاده بیش از حد از آن می‌تواند منجر به کد پیچیده و ناکارآمد شود. به جای آن می‌توانیم گزینه‌های زیر را در نظر بگیریم:

چک‌لیستی که قبل از استفاده از هوک useState باید به آن توجه کنیم عبارت است از:

اگر به همه موارد بالا پاسخ « نه » بدهیم، شاید اصلاً نیازی به استفاده از useState نداشته باشیم.

دریافت مقادیر بدون state

نکته‌ای که ممکن است کم‌تر شناخته شده باشد این است که مقادیر مشتق‌شده نیازی به ذخیره شدن در state ندارند. اگر داده ما می‌تواند از stateهای موجود یا props محاسبه شود، بهتر است آن را مستقیماً هنگام رندر محاسبه نماییم. برای مثال، فرمت‌بندی یک تاریخ را می‌توانیم بدون استفاده از هوک‌های اضافی انجام دهیم:

const formattedDate = new Date(date).toLocaleDateString();

کد بالا یک رشته تاریخ فرمت شده را از یک ورودی date محاسبه می‌کند، بدون اینکه آن را در state کامپوننت ذخیره نماید. با تعریف formattedDate به عنوان یک constant، مقدار آن در لحظه و هر بار که فراخوانی شود محاسبه می‌گردد و state فعلی date را منعکس می‌کند.

این رویکرد از مدیریت غیرضروری state جلوگیری می‌کند، منطق رندر را ساده‌تر کرده و کامپوننت را کارآمد نگه می‌دارد. زیرا، مقادیر مشتق‌شده فقط زمانی محاسبه می‌شوند که داده‌های اولیه تغییر کنند. بنابراین، این روش یک سبک برنامه‌نویسی تمیز و تابع محور را در React ترویج می‌دهد.

همینطور، این کار کامپوننت‌های ما را ساده‌تر کرده و از به‌روزرسانی‌های غیرضروری state جلوگیری می‌نماید.

محاسبه مقادیر بدون استفاده از Effectها

باید استفاده از هوک useEffect برای محاسبات ساده را متوقف کنیم. اگر مقادیر مورد نظر ما می‌تواند مستقیماً از state یا props محاسبه شود و شامل side effectها نیست، می‌توانیم آن را هنگام رندر محاسبه کنیم. همینطور، برای زمانی که محاسبات سنگین و پرهزینه داریم، می‌توانیم برای بهینه‌سازی عملکرد از هوک useMemo استفاده نماییم:

const expensiveValue = useMemo(() => computeExpensiveValue(data), [data]);This reduces the complexity of your code and keeps your components focused.

این کد از هوک useMemo برای محاسبه مقدار expensiveValue بر اساس ورودی data، بدون ایجاد side effect استفاده می‌کند. پس از آن، نتیجه computeExpensiveValue(data) را ذخیره می‌کند و فقط زمانی آن را دوباره محاسبه می‌کند که data تغییر کند. این روش، از محاسبات غیرضروری در هر رندر جلوگیری کرده و عملکرد را برای محاسبات سنگین بهبود می‌بخشد.

با استفاده از useMemo، کامپوننت مقدار را به طور کارآمد از props یا state فعلی خود مشتق می‌کند و فرآیند رندر را بهینه و متمرکز بر داده‌های جدید نگه می‌دارد.

یکتا بودن کلیدها

ممکن است در پروژه‌های خود از ایندکس‌های آرایه به عنوان کلید در لیست‌ها استفاده کرده باشیم. اما این موضوع می‌تواند باعث ایجاد باگ در برنامه شود. React برای شناسایی آیتم‌ها به کلیدهای یکتا وابسته است و استفاده از مقادیر غیریکتا می‌تواند باعث اختلال در عملکرد شود.

برای تولید شناسه‌های منحصربه‌فرد می‌توانیم از crypto.randomUUID() استفاده کنیم، اما باید مطمئن شویم که این کار را تنها زمانی که state ما به‌روزرسانی می‌شود انجام می‌دهیم، نه در هر رندر. یک ویژگی id به آبجکت‌ها اضافه می‌کنیم:

const itemWithId = items.map(item => ({ ...item, id: generateUniqueId() }));

کد بالا، یک آرایه جدید به نام itemWithId ایجاد می‌کند که در آن هر آیتم از آرایه items با یک id منحصربه‌فرد به آرایه جدید اضافه می‌شود.

عملگر spread (...item) ویژگی‌های هر آیتم را کپی می‌کند، در حالی که generateUniqueId() یک id جدید و منحصربه‌فرد تولید می‌نماید. این کار تضمین می‌کند که هر آیتم یک کلید متمایز داشته باشد که برای کامپوننت‌های React هنگام رندر لیست‌ها ضروری می‌باشد.

کلیدهای یکتا به React کمک می‌کنند تا به طور کارآمد بروزرسانی‌ها را مدیریت کرده، تغییرات را شناسایی کند و عملکرد رندر را بهینه نماید.

توجه به dependencyها

یکی از ویژگی‌های عجیبی که در React وجود دارد این است که فراموش کردن dependencyها در هوک useEffect می‌تواند منجر به stale closureها شود. برای مثال، اگر هوک useEffect وابستگی‌های لازم را نداشته باشد، ممکن است به‌روزرسانی نشود. بنابراین، همیشه باید آرایه‌های dependency خود را در نظر داشته باشیم:

useEffect(() => {// Effect logic}, [dependency]);

مثالی که داریم، یک side effect در یک کامپوننت React تعریف می‌کند که زمانی که dependency مشخص‌شده تغییر می‌کند، اجرا می‌شود. ضروری است که تمام dependencyهای مربوطه را در آرایه dependencyها قرار دهیم تا از عملکرد صحیح آن اطمینان حاصل نماییم.

حذف dependencyها می‌تواند منجر به استفاده از مقادیر قدیمی یا نادرست در side effect شود، زیرا React ممکن است آن را زمانی که نیاز است دوباره اجرا نکند. بنابراین، گنجاندن تمام dependencyها به حفظ همگام‌سازی بین state کامپوننت و side effect، اطمینان از رفتار پیش‌بینی‌شده و جلوگیری از باگ‌های احتمالی مرتبط با به‌روزرسانی‌های miss شده کمک می‌کند.

اگر رابط کاربری ما به درستی به‌روزرسانی نمی‌شود، این مشکل می‌تواند یکی از دلایل آن باشد.

استفاده از هوک useEffect در انتها

نکته‌ای که باید به آن توجه کنیم این است که نباید برای استفاده از هوک useEffect عجله داشته باشیم. هوک useEffect یک هوک قدرتمند است اما اگر بیش از حد از آن استفاده کنیم، می‌تواند باعث پیچیده‌تر شدن کد ما شود. فریم‌ورک‌های React راه‌حل‌هایی را برای مدیریت دقیق‌تر side effectها ارائه می‌دهند. می‌توانیم برای دریافت داده‌ها، از کتابخانه‌هایی مانند TanStack Query یا SWR استفاده کنیم که درخواست‌ها و کش را به طور مؤثری مدیریت می‌کنند و منجر به تجربه کاربری بهتری می‌شوند.

همچینین استراتژی‌های جایگزینی که وجود دارند عبارتند از:

جمع‌بندی

React یک کتابخانه قدرتمند است، اما دانستن نحوه استفاده مؤثر از آن می‌تواند هنگام ساخت پروژه‌های مختلف بسیار مفید باشد. ما در این مقاله سعی کردیم تا best practiceها در React را باهم بررسی کنیم. داشتن درک درست و عمیق از جزئیات هر فناوری به ما در طول توسعه و بهینه‌سازی کمک بسیار زیادی می‌کند. React بهترین کتابخانه برای توسعه مدرن است و همه چیز را برای توسعه و بهینه‌سازی ارائه می‌دهد.