useTransition vs useDeferredValue | React 18

-اول باید بدونیم concurrency چیه؟ React 18 یه مفهوم جدید به نام concurrent rendering رو ارائه داده که یعنی توانایی انجام چندین کار به طور همزمان. قبلا React تغییرات state هایی که همزمان اتفاق می افتادند رو گروه بندی میکرد و یکباره (به جای چند بار، یکبار بعد از هر تغییر state) کامپوننت رو rerender میکرد. که بهش Render-blocking گفته میشد اما الان concurrent Mode معرفی شده.

بریم سراغ یه مثال( این مثالو تو یه مقاله خوندم و جالب بود پس به شما هم میگم :) )

فرض کنید در حین نوشتن این مقاله من دارم همزمان غذا هم میپزم و الان زمان اضافه کردن مواد به غذاس (این کار ضروری تره پس الان میرم سراغ اون) پس از نوشتن دست میکشم و میرم تا مواد رو به غذا اضافه کنم بعد از تموم شدن کارم میام و نوشتن مقالمو ادامه میدم یعنی توی مراحل مختلف تمرکز من روی موارد ضروری تره.

کل داستان concurrent rendering در React با استفاده از useTransition و useDeferredValue قابل استفاده است پس بریم یادشون بگیریم.

-useTransition

-اول باید بدونیم useTransition چیه؟ بعضی از به‌روزرسانی‌های UI باید در سریع‌ترین زمان ممکن انجام بشه (مثل تایپ کردن تو یه input ، انتخاب یک value از dropdown ) در حالی که بعضی های دیگه میتونن اولویت کمتری داشته باشن (مثل فیلتر کردن یک لیست). قبل از React 18 ابزاری برای اولویت بندی به روز رسانی های UI نداشتیم اما خوشبختانه، React 18 به ما این امکان رو میده که به‌روزرسانی‌های UI رو اولویت بندی کنیم.

حالا useTransition به React میگه که بعضی از setState ها اولویت پایین‌تری دارن و فوری نیستن

بریم سراغ سینتکسش :

const [isPending, startTransition] = useTransition();

وقتی که ما useTransition رو فراخوانی می کنیم، یک آرایه با دو تا عنصر دریافت می کنیم: که اولیش یه مقدار بولین به نام isPending هست.(زمانی که تابع startTransition در حال اجراست true میشه و وقتی اجراش خاتمه پیدا میکنه false میشه) و دومیش تابع startTransition هست که باهاش setState ای رو که به روز رسانیش اولویت کمتری داره wrap میکنیم.

بریم سراغ یه مثال : وارد این لینک شو
تو این مثال یه لیست از محصولات و یه اینپوت برای سرچ کردن داریم. وقتی اسم محصول رو سرچ میکنیم اگر در لیست وجود داشته باشه لیست فلیتر میشه. وقتی به سرعت یه اسم رو توی اینپوت سرچ میکنیم متوجه تاخیر تو تایپ کردن میشیم.در صورتی که به روز رسانی اینپوت موقع تایپ کردن، یک کار فوریه و باید سریع انجام بشه. اما به روز رسانی لیست، یه کار سنگین و غیر فوریه ( این کار سنگین و غیر فوری، کار فوری و سبک رو کند میکنه). (راستی موقع تایپ کردن توی اینپوت، اگر سیستم کندی دارید متوجه این تاخیر میشید اما اگر سیستمتون خفنه :) این تاخیر رو نمیبینید پس inspect بگیرید و وارد تب performance بشید و از قسمت cpu گزینه 6x slowdown رو انتخاب کنید این کار cpu رو ۶ برابر کند میکنه. اینطوری کندی رو موقع تایپ کردن احساس میکنید)

-حالا چجوری این مشکلو حل کنیم؟ با استفاده از useTransition به React اطلاع میدیم که کدوم به‌روزرسانی‌های UI فوری هستن و کدوم غیر فوری. فقط کافیه توی codesandbox خط ۲۲ و همچنین خط ۲۵ تا ۲۷ رو از کامنت در بیارید (خط ۲۸ هم پاک کن دیگه بهش نیاز نداریم). راستی خط ۳۴ هم اگر از کامنت در بیاری میبینی با استفاده از isPending داریم یه پاراگراف رو نمایش میدیم.

-ما الان دقیقا چکار کردیم؟ وقتی به روزرسانی state از الویت کمتری برخورداره، با startTransition به روز رسانی state رو wrap میکنیم. بعد از این کار، فیلتر شدن لیستمون با سرچ کاربر sync نیست و مثلا ممکنه کاربر کلمه سرچ شده رو پاک کنه و اینپوت خالی باشه اما بعضی از نتایج رو در لیست ببینه (یعنی فیلتر شدن لیست عقب مونده) و این دقیقا همون کاریه که startTransition انجام میده ( به React گفتیم به روزرسانی لیست الویت کمتری نسبت به به روزرسانی اینپوت داره )

-کی ازش استفاده کنیم‌؟ وقتی رابط کاربری کندی داریم (مخصوصا توی دستگاه قدیمی تر) و مثلا نمیتونیم چنین فیلتر هایی رو سمت سرور هندل کنیم و باید سمت کلاینت انجام بشه.


-useDeferredValue

-چی هست اصلا؟ useDeferredValue دو تا آیتم به عنوان ورودی می گیره، در واقع یک مقدار رو دریافت می کنه و بعد از timeout خاصی اون رو برمی گردونه. به این صورت:

 const deferredValue = useDeferredValue(value, { timeoutMs: 2000 }); 

وقتی که value ارسال شده به useDeferredValue تغییر می کنه، بسته به اولویت render فعلی، مقدار قبلی یا مقدار جدید رو برمی گردونه. اگر رندر فعلی نتیجه یه به‌روزرسانی فوری باشد(مثل تایپ کردن در اینپوت)، React مقدار قبلی رو برمی‌گردونه. و بعد از اتمام render فعلی، به مقدار جدید تغییر پیدا میکنه.

به گفته Dan Abramov عزیز: زمانی از useDeferredValue استفاده میکنیم که دیتا از لایه های بالاتر بیاد و ما کنترلی بر فراخوانی setState مربوطه نداریم.

توی مثالی که زدیم ما این کنترل رو داریم، اما بیاید شبیه سازی کنیم که نداریم :)) فقط کافیه توی codesandbox، چیزهایی که قبلا گفتم از کامنت در بیاری رو دوباره کامنت کنی و داخل کامپوننت ProductList، این کد رو جایگزین کد فعلی کنی.

import { useDeferredValue } from &quotreact&quot
function ProductList({ products }) {
const deferredProducts = useDeferredValue(products);
return (
<ul>
{deferredProducts.map((product) => (
<li>{product}</li>
))}
</ul>
);}
export default ProductList;


-خلاصه: useTransition کد مربوط به setState رو wrap میکنه. در حالی که useDeferredValue هنگام دریافت دیتا از یک لایه های بالا، مقداری که تحت تاثیر تغییر state قرار می گیره رو wrap میکنه. شما نباید از هر دو به طور همزمان استفاده کنید چون هر دوشون یه کار رو انجام میدن. اگر به کد مربوط به به‌روزرسانی state دسترسی دارید و به‌روزرسانی‌ state از اولویت کمتری برخورداره، منطقیه که از useTransition استفاده کنید و اگر دسترسی ندارید از useDeferredValue استفاده کنید. در کل اگر رابط کاربری خوبی ندارید و نمیتونید به روش دیگه ای بهینه اش کنید از این Hook ها استفاده کنید. (منظورم از روش های دیگه:lazy loading و pagination و هندل کردن فیلتر سمت سرور و ... است.)