startTransition: علامت‌گذاری یک به‌روزرسانی به‌عنوان Transition

در درس قبل، مفهوم Transition را به‌صورت نظری دیدیم. در این درس، به اولین ابزار عملی برای اعمال این مفهوم می‌پردازیم: تابع startTransition. این تابع، ساده‌ترین و اولیه‌ترین راه برای گفتن به React است که «این به‌روزرسانی خاص، می‌تواند با اولویت پایین‌تر انجام شود».

نحو پایه

jsx
import { startTransition } from "react";

function handleTabChange(nextTab) {
  startTransition(() => {
    setTab(nextTab);
  });
}

startTransition، یک تابع می‌گیرد؛ هر فراخوانی set که داخل این تابع انجام شود، به‌جای یک به‌روزرسانی فوری معمولی، به‌عنوان یک Transition علامت‌گذاری می‌شود.

تفاوت با یک setTab معمولی

jsx
// فوری: React تلاش می‌کند بلافاصله کامل اجرا شود
function handleTabChangeUrgent(nextTab) {
  setTab(nextTab);
}

// Transition: React اجازه دارد این را کم‌اولویت‌تر در نظر بگیرد
function handleTabChangeTransition(nextTab) {
  startTransition(() => {
    setTab(nextTab);
  });
}

از نظر نتیجه نهایی روی صفحه، هر دو نسخه، در نهایت همان tab جدید را نشان می‌دهند. تفاوت، فقط در رفتار React در طول رسیدن به آن نتیجه است: در نسخه Transition، اگر در همین حین، یک رویداد فوری‌تر (مثل یک کلیک دیگر) برسد، React می‌تواند به آن، زودتر رسیدگی کند، حتی اگر Render مربوط به Transition هنوز کامل نشده باشد.

مثال کامل: تغییر تب با یک لیست سنگین

jsx
function ProfilePage() {
  const [tab, setTab] = useState("posts");

  function selectTab(nextTab) {
    startTransition(() => {
      setTab(nextTab);
    });
  }

  return (
    <div>
      <button onClick={() => selectTab("posts")}>پست‌ها</button>
      <button onClick={() => selectTab("about")}>درباره</button>

      {tab === "posts" ? <HeavyPostsList /> : <AboutTab />}
    </div>
  );
}

با این تغییر، حتی اگر HeavyPostsList رندرش کمی طول بکشد، خود دکمه‌ها همچنان فوری به کلیک بعدی واکنش نشان می‌دهند؛ چون React، محاسبه مربوط به Transition را، در صورت نیاز، به نفع یک به‌روزرسانی فوری‌تر، به تعویق می‌اندازد.

محدودیت مهم: فقط برای set داخل خود همان رویداد

startTransition، باید همان‌جایی فراخوانی شود که خود تغییر State رخ می‌دهد؛ معمولاً مستقیم داخل یک Event Handler. نمی‌توان یک تابع async را مستقیم به startTransition داد و منتظر ماند تا یک درخواست شبکه (مانند الگوهای فصل چهاردهم) تمام شود؛ خود تابعی که به آن می‌دهیم، باید هم‌زمان (Synchronous) باشد و فقط شامل فراخوانی‌های set.

startTransition، مقداری برای نمایش وضعیت در حال انتظار نمی‌دهد

یک محدودیت دیگر startTransition: این تابع، خودش هیچ راهی برای دانستن «آیا این Transition هنوز در حال اجراست یا تمام شده» در اختیار نمی‌گذارد. اگر بخواهیم، مثلاً، یک نشانگر بصری ملایم (نه یک Spinner کامل) در طول این انتظار نشان دهیم، به ابزاری نیاز داریم که همین وضعیت را هم برگرداند؛ این دقیقاً چیزی است که در درس بعدی، با Hook useTransition (که این قابلیت را به‌صورت یک Hook کامل‌تر ارائه می‌دهد)، می‌بینیم.

چرا این دو (تابع و Hook) هر دو وجود دارند

startTransition، یک تابع مستقل است که در هر جایی از کد (حتی خارج از کامپوننت‌ها) قابل فراخوانی است؛ اما چون State مربوط به «در حال انتظار بودن» را برنمی‌گرداند، برای اکثر موارد عملی داخل کامپوننت‌ها، نسخه Hook آن (useTransition) ترجیح داده می‌شود. در درس بعدی، این تفاوت را با جزئیات کامل می‌بینیم.

مثال قابل اجرا

مقایسه تغییر تب فوری و تغییر تب با startTransition
import { useState, startTransition } from "react";

function HeavyList({ label }) {
  const items = Array.from({ length: 10000 }, (_, i) => `${label} ${i}`);
  return (
    <ul>
      {items.slice(0, 15).map((item) => (
        <li key={item}>{item}</li>
      ))}
    </ul>
  );
}

function App() {
  const [tab, setTab] = useState("posts");

  function selectTab(nextTab) {
    startTransition(() => {
      setTab(nextTab);
    });
  }

  return (
    <div>
      <button onClick={() => selectTab("posts")}>پست‌ها</button>
      <button onClick={() => selectTab("about")}>درباره</button>
      <HeavyList label={tab === "posts" ? "پست" : "اطلاعات"} />
    </div>
  );
}
برای دیدن پیش‌نمایش کامپوننت، روی Run بزنید.

جمع‌بندی

startTransition، هر فراخوانی set داخل خودش را به‌عنوان یک به‌روزرسانی کم‌اولویت‌تر به React اعلام می‌کند؛ این تابع باید هم‌زمان باشد و فقط شامل تغییرات State، نه یک عملیات Async کامل. این تابع، وضعیت «در حال انتظار» را برنمی‌گرداند؛ برای آن، در درس بعدی، به useTransition می‌پردازیم.