فارسی

بررسی عمیق هوک useDeferredValue در ری‌اکت. بیاموزید چگونه تأخیر UI را رفع کنید، همزمانی را درک کنید، با useTransition مقایسه کنید و اپلیکیشن‌های سریع‌تری برای مخاطبان جهانی بسازید.

useDeferredValue در ری‌اکت: راهنمای جامع برای عملکرد غیرمسدودکننده UI

در دنیای توسعه وب مدرن، تجربه کاربری (UX) در درجه اول اهمیت قرار دارد. یک رابط کاربری سریع و پاسخگو دیگر یک ویژگی لوکس نیست، بلکه یک انتظار است. برای کاربران در سراسر جهان، با طیف گسترده‌ای از دستگاه‌ها و شرایط شبکه، یک UI کند و دارای لگ می‌تواند تفاوت بین یک مشتری بازگشتی و یک مشتری از دست رفته باشد. اینجاست که ویژگی‌های همزمان (concurrent) ری‌اکت ۱۸، به ویژه هوک useDeferredValue، بازی را تغییر می‌دهد.

اگر تا به حال یک اپلیکیشن ری‌اکت با یک فیلد جستجو ساخته‌اید که یک لیست بزرگ را فیلتر می‌کند، یک گرید داده که به صورت زنده آپدیت می‌شود، یا یک داشبورد پیچیده، احتمالاً با فریز شدن ناخوشایند UI مواجه شده‌اید. کاربر تایپ می‌کند و برای یک لحظه، کل اپلیکیشن غیرپاسخگو می‌شود. این اتفاق می‌افتد زیرا رندرینگ سنتی در ری‌اکت مسدودکننده (blocking) است. یک آپدیت state باعث یک رندر مجدد می‌شود و تا زمانی که تمام نشود، هیچ کار دیگری نمی‌تواند انجام شود.

این راهنمای جامع شما را به یک بررسی عمیق از هوک useDeferredValue می‌برد. ما مشکلی که این هوک حل می‌کند، نحوه کارکرد آن در پشت صحنه با موتور همزمان جدید ری‌اکت، و چگونگی استفاده از آن برای ساخت اپلیکیشن‌های فوق‌العاده پاسخگو که حتی در هنگام انجام کارهای سنگین، سریع به نظر می‌رسند را بررسی خواهیم کرد. ما مثال‌های عملی، الگوهای پیشرفته و بهترین شیوه‌های حیاتی برای مخاطبان جهانی را پوشش خواهیم داد.

درک مشکل اصلی: UI مسدودکننده

قبل از اینکه بتوانیم راه حل را درک کنیم، باید مشکل را به طور کامل بشناسیم. در نسخه‌های ری‌اکت قبل از ۱۸، رندرینگ یک فرآیند همگام (synchronous) و غیرقابل وقفه بود. یک جاده تک‌بانده را تصور کنید: وقتی یک ماشین (یک رندر) وارد آن می‌شود، هیچ ماشین دیگری نمی‌تواند عبور کند تا زمانی که آن ماشین به انتهای جاده برسد. ری‌اکت اینگونه کار می‌کرد.

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

یک پیاده‌سازی معمولی (و کند)

در اینجا کدی را می‌بینید که ممکن است در دنیای قبل از ری‌اکت ۱۸ یا بدون استفاده از ویژگی‌های همزمان به نظر برسد:

ساختار کامپوننت:

فایل: SearchPage.js

import React, { useState } from 'react'; import ProductList from './ProductList'; import { generateProducts } from './data'; // a function that creates a large array const allProducts = generateProducts(20000); // Let's imagine 20,000 products function SearchPage() { const [query, setQuery] = useState(''); const filteredProducts = allProducts.filter(product => { return product.name.toLowerCase().includes(query.toLowerCase()); }); function handleChange(e) { setQuery(e.target.value); } return (

); } export default SearchPage;

چرا این کند است؟

بیایید عملکرد کاربر را ردیابی کنیم:

  1. کاربر یک حرف، مثلاً 'a' را تایپ می‌کند.
  2. رویداد onChange فعال شده و handleChange را فراخوانی می‌کند.
  3. setQuery('a') فراخوانی می‌شود. این یک رندر مجدد برای کامپوننت SearchPage را زمان‌بندی می‌کند.
  4. ری‌اکت رندر مجدد را شروع می‌کند.
  5. در داخل رندر، خط const filteredProducts = allProducts.filter(...) اجرا می‌شود. این بخش پرهزینه است. فیلتر کردن یک آرایه ۲۰,۰۰۰ آیتمی، حتی با یک بررسی ساده 'includes'، زمان‌بر است.
  6. در حالی که این فیلترینگ در حال انجام است، رشته اصلی (main thread) مرورگر کاملاً اشغال است. نمی‌تواند هیچ ورودی جدیدی از کاربر را پردازش کند، نمی‌تواند فیلد ورودی را به صورت بصری آپدیت کند و نمی‌تواند هیچ جاوااسکریپت دیگری را اجرا کند. UI مسدود شده است.
  7. هنگامی که فیلترینگ تمام شد، ری‌اکت به رندر کردن کامپوننت ProductList ادامه می‌دهد، که خود ممکن است یک عملیات سنگین باشد اگر هزاران نود DOM را رندر کند.
  8. در نهایت، پس از همه این کارها، DOM آپدیت می‌شود. کاربر حرف 'a' را در کادر ورودی می‌بیند و لیست آپدیت می‌شود.

اگر کاربر به سرعت تایپ کند - مثلاً "apple" - این فرآیند مسدودکننده برای 'a'، سپس 'ap'، سپس 'app'، 'appl' و 'apple' اتفاق می‌افتد. نتیجه یک تأخیر قابل توجه است که در آن فیلد ورودی دچار لکنت شده و برای همگام شدن با تایپ کاربر تلاش می‌کند. این یک تجربه کاربری ضعیف است، به ویژه در دستگاه‌های ضعیف‌تر که در بسیاری از نقاط جهان رایج هستند.

معرفی همزمانی (Concurrency) در ری‌اکت ۱۸

ری‌اکت ۱۸ با معرفی همزمانی، این پارادایم را به طور اساسی تغییر می‌دهد. همزمانی با موازی‌سازی (انجام چندین کار به طور همزمان) یکسان نیست. بلکه، این توانایی ری‌اکت برای مکث، از سرگیری یا رها کردن یک رندر است. جاده تک‌بانده اکنون دارای خطوط سبقت و یک کنترل‌کننده ترافیک است.

با همزمانی، ری‌اکت می‌تواند آپدیت‌ها را به دو نوع طبقه‌بندی کند:

ری‌اکت اکنون می‌تواند یک رندر غیرفوری "انتقالی" را شروع کند، و اگر یک آپدیت فوری‌تر (مانند یک کلید دیگر) وارد شود، می‌تواند رندر طولانی را متوقف کرده، ابتدا آپدیت فوری را مدیریت کند و سپس کار خود را از سر بگیرد. این تضمین می‌کند که UI همیشه تعاملی باقی بماند. هوک useDeferredValue یک ابزار اصلی برای بهره‌برداری از این قدرت جدید است.

`useDeferredValue` چیست؟ یک توضیح دقیق

در هسته خود، useDeferredValue هوکی است که به شما امکان می‌دهد به ری‌اکت بگویید که یک مقدار مشخص در کامپوننت شما فوری نیست. این هوک یک مقدار را می‌پذیرد و یک کپی جدید از آن مقدار را برمی‌گرداند که در صورت وقوع آپدیت‌های فوری، "عقب می‌ماند".

سینتکس

استفاده از این هوک فوق‌العاده ساده است:

import { useDeferredValue } from 'react'; const deferredValue = useDeferredValue(value);

همین. شما یک مقدار به آن پاس می‌دهید و یک نسخه تأخیری (deferred) از آن مقدار را به شما می‌دهد.

چگونه در پشت صحنه کار می‌کند

بیایید این جادو را رمزگشایی کنیم. وقتی از useDeferredValue(query) استفاده می‌کنید، این کاری است که ری‌اکت انجام می‌دهد:

  1. رندر اولیه: در اولین رندر، deferredQuery با query اولیه یکسان خواهد بود.
  2. یک آپدیت فوری رخ می‌دهد: کاربر یک کاراکتر جدید تایپ می‌کند. state مربوط به query از 'a' به 'ap' آپدیت می‌شود.
  3. رندر با اولویت بالا: ری‌اکت بلافاصله یک رندر مجدد را آغاز می‌کند. در طول این رندر اول و فوری، useDeferredValue می‌داند که یک آپدیت فوری در حال انجام است. بنابراین، هنوز مقدار قبلی، یعنی 'a' را برمی‌گرداند. کامپوننت شما به سرعت رندر مجدد می‌شود زیرا مقدار فیلد ورودی 'ap' می‌شود (از state)، اما بخشی از UI شما که به deferredQuery بستگی دارد (لیست کند) هنوز از مقدار قدیمی استفاده می‌کند و نیازی به محاسبه مجدد ندارد. UI پاسخگو باقی می‌ماند.
  4. رندر با اولویت پایین: درست پس از اتمام رندر فوری، ری‌اکت یک رندر دوم و غیرفوری را در پس‌زمینه شروع می‌کند. در *این* رندر، useDeferredValue مقدار جدید، 'ap' را برمی‌گرداند. این رندر پس‌زمینه همان چیزی است که عملیات فیلترینگ پرهزینه را آغاز می‌کند.
  5. قابلیت وقفه: این بخش کلیدی است. اگر کاربر حرف دیگری ('app') را تایپ کند در حالی که رندر با اولویت پایین برای 'ap' هنوز در حال انجام است، ری‌اکت آن رندر پس‌زمینه را دور می‌اندازد و از نو شروع می‌کند. این آپدیت فوری جدید ('app') را در اولویت قرار می‌دهد و سپس یک رندر پس‌زمینه جدید با آخرین مقدار تأخیری را زمان‌بندی می‌کند.

این تضمین می‌کند که کارهای پرهزینه همیشه بر روی جدیدترین داده‌ها انجام می‌شود و هرگز مانع از ارائه ورودی جدید توسط کاربر نمی‌شود. این یک راه قدرتمند برای کاهش اولویت محاسبات سنگین بدون منطق پیچیده دستی debouncing یا throttling است.

پیاده‌سازی عملی: رفع جستجوی کند ما

بیایید مثال قبلی خود را با استفاده از useDeferredValue بازنویسی کنیم تا آن را در عمل ببینیم.

فایل: SearchPage.js (بهینه‌سازی شده)

import React, { useState, useDeferredValue, useMemo } from 'react'; import ProductList from './ProductList'; import { generateProducts } from './data'; const allProducts = generateProducts(20000); // A component to display the list, memoized for performance const MemoizedProductList = React.memo(ProductList); function SearchPage() { const [query, setQuery] = useState(''); // 1. Defer the query value. This value will lag behind the 'query' state. const deferredQuery = useDeferredValue(query); // 2. The expensive filtering is now driven by the deferredQuery. // We also wrap this in useMemo for further optimization. const filteredProducts = useMemo(() => { console.log('Filtering for:', deferredQuery); return allProducts.filter(product => { return product.name.toLowerCase().includes(deferredQuery.toLowerCase()); }); }, [deferredQuery]); // Only re-calculates when deferredQuery changes function handleChange(e) { // This state update is urgent and will be processed immediately setQuery(e.target.value); } return (

{/* 3. The input is controlled by the high-priority 'query' state. It feels instant. */} {/* 4. The list is rendered using the result of the deferred, low-priority update. */}
); } export default SearchPage;

تحول در تجربه کاربری

با این تغییر ساده، تجربه کاربری متحول می‌شود:

اپلیکیشن اکنون به طور قابل توجهی سریع‌تر و حرفه‌ای‌تر احساس می‌شود.

`useDeferredValue` در مقابل `useTransition`: تفاوت چیست؟

این یکی از رایج‌ترین نقاط سردرگمی برای توسعه‌دهندگانی است که ری‌اکت همزمان را یاد می‌گیرند. هر دو useDeferredValue و useTransition برای علامت‌گذاری آپدیت‌ها به عنوان غیرفوری استفاده می‌شوند، اما در شرایط متفاوتی به کار می‌روند.

تمایز کلیدی این است: کنترل در دست شما کجاست؟

`useTransition`

شما از useTransition زمانی استفاده می‌کنید که کنترل کدی را دارید که آپدیت state را آغاز می‌کند. این هوک یک تابع به شما می‌دهد که معمولاً startTransition نامیده می‌شود تا آپدیت state خود را در آن بپیچید.

const [isPending, startTransition] = useTransition(); function handleChange(e) { const nextValue = e.target.value; // Update the urgent part immediately setInputValue(nextValue); // Wrap the slow update in startTransition startTransition(() => { setSearchQuery(nextValue); }); }

`useDeferredValue`

شما از useDeferredValue زمانی استفاده می‌کنید که کنترل کدی را ندارید که مقدار را آپدیت می‌کند. این اغلب زمانی اتفاق می‌افتد که مقدار از props، از یک کامپوننت والد یا از هوک دیگری که توسط یک کتابخانه شخص ثالث ارائه شده است، می‌آید.

function SlowList({ valueFromParent }) { // We don't control how valueFromParent is set. // We just receive it and want to defer rendering based on it. const deferredValue = useDeferredValue(valueFromParent); // ... use deferredValue to render the slow part of the component }

خلاصه مقایسه

ویژگی `useTransition` `useDeferredValue`
چه چیزی را می‌پیچد یک تابع آپدیت state (مثلاً startTransition(() => setState(...))) یک مقدار (مثلاً useDeferredValue(myValue))
نقطه کنترل زمانی که شما کنترل کننده رویداد یا آغازگر آپدیت را کنترل می‌کنید. زمانی که یک مقدار (مثلاً از props) دریافت می‌کنید و کنترلی بر منبع آن ندارید.
وضعیت بارگذاری یک بولین `isPending` داخلی ارائه می‌دهد. فلگ داخلی ندارد، اما می‌توان آن را با `const isStale = originalValue !== deferredValue;` استخراج کرد.
تشبیه شما متصدی اعزام هستید و تصمیم می‌گیرید کدام قطار (آپدیت state) در مسیر کند حرکت کند. شما مدیر ایستگاه هستید، می‌بینید که یک مقدار با قطار می‌رسد و تصمیم می‌گیرید آن را برای لحظه‌ای در ایستگاه نگه دارید قبل از اینکه آن را روی تابلوی اصلی نمایش دهید.

موارد استفاده و الگوهای پیشرفته

فراتر از فیلتر کردن لیست‌های ساده، useDeferredValue چندین الگوی قدرتمند برای ساخت رابط‌های کاربری پیچیده را باز می‌کند.

الگوی ۱: نمایش یک UI "کهنه" به عنوان بازخورد

یک UI که با کمی تأخیر و بدون هیچ بازخورد بصری آپدیت می‌شود، می‌تواند برای کاربر حس باگ داشته باشد. آنها ممکن است از خود بپرسند که آیا ورودی آنها ثبت شده است یا نه. یک الگوی عالی، ارائه یک نشانه ظریف است که داده‌ها در حال آپدیت شدن هستند.

شما می‌توانید با مقایسه مقدار اصلی با مقدار تأخیری به این هدف برسید. اگر آنها متفاوت باشند، به این معنی است که یک رندر پس‌زمینه در انتظار است.

function SearchPage() { const [query, setQuery] = useState(''); const deferredQuery = useDeferredValue(query); // This boolean tells us if the list is lagging behind the input const isStale = query !== deferredQuery; const filteredProducts = useMemo(() => { // ... expensive filtering using deferredQuery }, [deferredQuery]); return (

setQuery(e.target.value)} />
); }

در این مثال، به محض اینکه کاربر تایپ می‌کند، isStale برابر با true می‌شود. لیست کمی کم‌رنگ می‌شود و نشان می‌دهد که در شرف آپدیت شدن است. هنگامی که رندر تأخیری کامل شد، query و deferredQuery دوباره برابر می‌شوند، isStale برابر با false می‌شود و لیست با داده‌های جدید به شفافیت کامل بازمی‌گردد. این معادل فلگ isPending از useTransition است.

الگوی ۲: به تعویق انداختن آپدیت‌ها در نمودارها و تجسم‌سازی‌ها

یک تجسم‌سازی داده پیچیده را تصور کنید، مانند یک نقشه جغرافیایی یا یک نمودار مالی، که بر اساس یک اسلایدر کنترل‌شده توسط کاربر برای یک محدوده تاریخ، مجدداً رندر می‌شود. کشیدن اسلایدر می‌تواند بسیار کند باشد اگر نمودار با هر پیکسل حرکت مجدداً رندر شود.

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

function ChartDashboard() { const [year, setYear] = useState(2023); const deferredYear = useDeferredValue(year); // HeavyChart is a memoized component that does expensive calculations // It will only re-render when the deferredYear value settles. const chartData = useMemo(() => computeChartData(deferredYear), [deferredYear]); return (

setYear(parseInt(e.target.value, 10))} /> Selected Year: {year}
); }

بهترین شیوه‌ها و دام‌های رایج

در حالی که useDeferredValue قدرتمند است، باید با احتیاط استفاده شود. در اینجا چند مورد از بهترین شیوه‌های کلیدی برای پیروی آورده شده است:

تأثیر بر تجربه کاربری جهانی (UX)

پذیرش ابزارهایی مانند useDeferredValue فقط یک بهینه‌سازی فنی نیست؛ بلکه تعهدی به یک تجربه کاربری بهتر و فراگیرتر برای مخاطبان جهانی است.

نتیجه‌گیری

هوک useDeferredValue ری‌اکت یک تغییر پارادایم در نحوه برخورد ما با بهینه‌سازی عملکرد است. به جای تکیه بر تکنیک‌های دستی و اغلب پیچیده مانند debouncing و throttling، اکنون می‌توانیم به صورت اعلانی به ری‌اکت بگوییم کدام بخش‌های UI ما اهمیت کمتری دارند و به آن اجازه دهیم کار رندر را به روشی بسیار هوشمندانه‌تر و کاربرپسندتر زمان‌بندی کند.

با درک اصول اصلی همزمانی، دانستن زمان استفاده از useDeferredValue در مقابل useTransition، و به کارگیری بهترین شیوه‌ها مانند مموایز کردن و بازخورد کاربر، می‌توانید لگ UI را از بین ببرید و اپلیکیشن‌هایی بسازید که نه تنها کاربردی، بلکه استفاده از آنها لذت‌بخش است. در یک بازار رقابتی جهانی، ارائه یک تجربه کاربری سریع، پاسخگو و در دسترس، ویژگی نهایی است و useDeferredValue یکی از قدرتمندترین ابزارها در زرادخانه شما برای دستیابی به آن است.