Skip to content

تعلیق - Suspense

ویژگی آزمایشی

<Suspense> یک ویژگی آزمایشی است و تضمینی برای دستیابی به وضعیت پایدار وجود ندارد. API آن ممکن است قبل از استقرار نهایی تغییر کند.

<Suspense> یک کامپوننت داخلی برای هماهنگ کردن وابستگی‌های ناهمگام در یک درخت کامپوننتی است. این کامپوننت می‌تواند وضعیت بارگیری را نمایش دهد در حالی که منتظر تکمیل وابستگی‌های ناهمزمان مختلفی در درخت کامپوننتی است، که این باعث می‌شود تا تجربه کاربری بهتری فراهم شود.

وابستگی‌های غیر همگام

برای توضیح مشکلی که <Suspense> سعی در حل آن دارد و نحوه تعامل آن با این وابستگی‌های ناهمگام، سلسله مراتب کامپوننتی زیر را در نظر بگیرید:

<Suspense>
└─ <Dashboard>
   ├─ <Profile>
   │  └─ <FriendStatus> (component with async setup())
   └─ <Content>
      ├─ <ActivityFeed> (async component)
      └─ <Stats> (async component)

در درخت کامپوننت، چندین کامپوننت تو در تو هستند که نمایش آن‌ها به منابع ناهمگام وابسته است که باید ابتدا تفکیک شوند. بدون <Suspense>، هر کدام از این کامپوننت‌ها باید وضعیت‌های خطا و بارگذاری خودش را مدیریت کند. در بدترین حالت ممکن، ممکن است سه نماد بارگذاری روی صفحه ببینیم و محتوا در زمان‌های مختلفی نمایش داده شود.

کامپوننت <Suspense> به ما این امکان را می‌دهد که در ابتدا وضعیت‌های بارگذاری/خطا با اولویت بالا را نمایش دهیم، در حالی که منتظر رفع وابستگی‌های ناهمگام تودرتو هستیم.

دو نوع وابستگی ناهمگام وجود دارد که <Suspense> می‌تواند منتظر آن‌ها بماند:

  1. کامپوننت‌ها با یک هوک setup()‎ ناهمگام. این شامل کامپوننت‌هایی است که از <script setup> با عبارات await استفاده می‌کنند.

  2. کامپوننت‌های ناهمگام.

async setup()‎

هوک setup()‎ یک کامپوننت Composition API می‌تواند ناهمگام باشد:

js
export default {
  async setup() {
    const res = await fetch(...)
    const posts = await res.json()
    return {
      posts
    }
  }
}

اگر از <script setup> استفاده می‌شود، حضور عبارات await به طور خودکار کامپوننت را به یک وابستگی ناهمگام تبدیل می‌کند.

vue
<script setup>
const res = await fetch(...)
const posts = await res.json()
</script>

<template>
  {{ posts }}
</template>

کامپوننت‌های ناهمگام

کامپوننت‌های ناهمگام به طور پیش‌فرض قابل تعلیق هستند. در این حالت، وضعیت بارگذاری توسط <Suspense> کنترل می‌شود و گزینه‌های بارگذاری، خطا، delay و time-out خود کامپوننت نادیده گرفته می‌شوند.

کامپوننت ناهمگام می‌تواند از کنترل <Suspense> خارج شده و با تعیین suspensible: false وضعیت بارگذاری خود را کنترل کند.

وضعیت بارگذاری

کامپوننت <Suspense> دارای دو اسلات به نام‌های ‎#default و ‎#fallback است. هر دو این قسمت‌ها فقط می‌توانند حاوی یک عنصر اصلی باشند. اگر امکان نمایش محتوای داخل ‎#default وجود داشته باشد، آن محتوا نمایش داده می‌شود. در صورتی که این امکان وجود نداشته باشد، محتوای داخل ‎#fallback نمایش داده می‌شود.

template
<Suspense>
  <!-- component with nested async dependencies -->
  <Dashboard />

  <!-- loading state via #fallback slot -->
  <template #fallback>
    Loading...
  </template>
</Suspense>

در رندر اولیه، <Suspense> محتوای اسلات default را در حافظه نمایش می‌دهد. اگر در طول این فرآیند به وابستگی‌های ناهمگامی برخورد شود، وارد وضعیت در انتظار می‌شود. در حالت در انتظار، محتوای اسلات fallback نمایش داده می‌شود. زمانی که تمام وابستگی‌های ناهمگام برطرف شده باشند، <Suspense> وارد وضعیت resolved می‌شود و محتوای اسلات default نمایش داده می‌شود.

اگر هیچ وابستگی ناهمگامی در رندر اولیه مشاهده نشد، <Suspense> مستقیماً به حالت resolved می‌رود.

<Suspense> را مانند یک سوئیچ در نظر بگیرید. وقتی در حالت resolved است، یعنی همه چیز آماده است و محتوا را نشان می دهد. تنها در صورتی به حالت در انتظار برمی گردد که گره ریشه در اسلات پیش فرض تغییر کند. اما اگر وابستگی‌های ناهمگام جدید که در عمق درخت جا دارند تغییر کنند، <Suspense> به حالت در انتظار بر نمی‌گردد.

هنگامی که یک بازگشت اتفاق می‌افتد، محتوای fallback به صورت فوری نمایش داده نمی‌شود، بلکه <Suspense> محتوای قبلی اسلات default را نمایش می دهد. این عملکرد می‌تواند با استفاده از پراپ timeout کانفیگ شود: اگر نمایش محتوای default جدید بیش از مهلت زمانی طول بکشد، <Suspense> به محتوای fallback تغییر خواهد کرد. مهلت زمانی 0 هم باعث می شود که محتوای fallback بلافاصله پس از جایگزینی، نمایش داده شود.

رویدادها

کامپوننت <Suspense> سه رویداد را ایجاد می‌کند: pending و resolve و fallback. رویداد pending در زمان ورود به وضعیت در انتظار رخ می‌دهد. رویداد resolve زمانی رخ می‌دهد که بارگذاری محتوای جدید در اسلات default به پایان برسد. رویداد fallback نیز زمانی اتفاق می‌افتد که محتوای اسلات fallback نمایش داده می‌شود.

می‌توان از رویدادها به عنوان مثال برای نمایش یک نماد بارگذاری در روی DOM قدیمی هنگامی که کامپوننت‌های جدید در حال بارگذاری هستند، استفاده کرد.

مدیریت خطا

در حال حاضر <Suspense> قابلیت مدیریت خطا از طریق خود کامپوننت را فراهم نمی‌کند - با این حال، شما می‌توانید از گزینه errorCaptured یا هوک onErrorCaptured()‎ برای گرفتن و مدیریت خطاهای ناهمگام در کامپوننت والد <Suspense> استفاده کنید.

ترکیب با کامپوننت‌های دیگر

معمول است که بخواهیم از کامپوننت <Suspense> به همراه کامپوننت‌های <Transition> و <KeepAlive> استفاده کنیم. ترتیب تو در توی این کامپوننت‌ها مهم است تا همگی به درستی کار کنند.

به علاوه، این کامپوننت‌ها معمولا با کامپوننت <RouterView> از Vue Router به کار می‌روند.

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

template
<RouterView v-slot="{ Component }">
  <template v-if="Component">
    <Transition mode="out-in">
      <KeepAlive>
        <Suspense>
          <!-- main content -->
          <component :is="Component"></component>

          <!-- loading state -->
          <template #fallback>
            Loading...
          </template>
        </Suspense>
      </KeepAlive>
    </Transition>
  </template>
</RouterView>

Vue Router برای بارگذاری تاخیری کامپوننت‌ها با استفاده از ایمپورت های پویا، پشتیبانی داخلی دارد. اینها از کامپوننت‌های ناهمگام متمایز هستند و در حال حاضر باعث فراخوانی <Suspense> نمی‌شوند. با این حال، آن‌ها همچنان می‌توانند کامپوننت‌های ناهمگام به عنوان فرزندان داشته باشند که این کامپوننت‌های ناهمگام می‌توانند <Suspense> را به روش معمول فراخوانی کنند.

Suspense تو در تو

زمانی که چند کامپوننت asynchrenous (رایج برای مسیرهای لایه بندی شده یا مبتنی بر طرح) مانند این داریم:

template
<Suspense>
  <component :is="DynamicAsyncOuter">
    <component :is="DynamicAsyncInner" />
  </component>
</Suspense>

<Suspense> یک مرز ایجاد می‌کند که همه کامپوننت های asynchrenous در زیر درخت را حل می‌کند، مطابق انتظار. با این حال، وقتی DynamicAsyncOuter را تغییر می‌دهیم، <Suspense> به درستی آن را در انتظار می‌گیرد، اما وقتی DynamicAsyncInner را تغییر می‌دهیم، DynamicAsyncInner تو در تو یک نود خالی تا زمان حل شدن آن رندر می‌کند (به جای قبلی یا اسلات پشتیبان).

برای حل این مشکل، می‌توانیم یک Suspense تو در تو داشته باشیم تا کامپوننت تو در تو را مدیریت کند، مانند:

template
<Suspense>
  <component :is="DynamicAsyncOuter">
    <Suspense suspensible> <!-- this -->
      <component :is="DynamicAsyncInner" />
    </Suspense>
  </component>
</Suspense>

اگر suspensible را تنظیم نکنید، <Suspense> داخلی مانند یک کامپوننت sync توسط <Suspense> والد در نظر گرفته می‌شود. این بدان معنی است که خودش اسلات پشتیبان دارد و اگر هر دو کامپوننت Dynamic در همان زمان تغییر کنند، ممکن است نودهای خالی و چرخه‌های وصله‌گیری متعدد در حالی که <Suspense> فرزند درخت وابستگی خود را بارگیری می‌کند، وجود داشته باشد که مطلوب نباشد. وقتی تنظیم شود، تمام مدیریت وابستگی async به <Suspense> والد داده می‌شود (شامل رویدادهای منتشر شده) و <Suspense> داخلی صرفاً به عنوان یک مرز دیگر برای حل وابستگی و وصله‌گیری عمل می‌کند.


مرتبط

تعلیق - Suspense has loaded