Chào mừng đến với Cafedev – nơi chia sẻ kiến thức và kinh nghiệm về công nghệ! Hôm nay, chúng ta sẽ cùng nhau tìm hiểu về chủ đề Vuejs with Suspense – một tính năng mới và hấp dẫn của Vue.js. Trong quá trình phát triển ứng dụng web, việc quản lý các phụ thuộc async có thể gây ra nhiều thách thức. Với Suspense, Vue.js cung cấp một giải pháp tiện lợi để xử lý các tình huống này một cách hiệu quả và linh hoạt. Hãy cùng khám phá và tìm hiểu thêm về Vuejs with Suspense ngay bây giờ!

Cảnh báo: Tính năng Thử nghiệm, <Suspense> là một tính năng thử nghiệm. Không đảm bảo sẽ đạt được tình trạng ổn định và API có thể thay đổi trước khi đạt được điều đó.

<Suspense> là một thành phần tích hợp sẵn để điều phối các phụ thuộc async trong cây thành phần. Nó có thể hiển thị trạng thái đang tải trong khi đợi các phụ thuộc async lồng nhau nhiều lớp xuống cây thành phần được giải quyết.

1. Các Phụ Thuộc Async

Để giải thích vấn đề mà đang cố gắng giải quyết và cách nó tương tác với các phụ thuộc async này, hãy tưởng tượng một cấu trúc thành phần như sau:

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


Trong cây thành phần này có nhiều thành phần lồng nhau mà việc render của chúng phụ thuộc vào một tài nguyên async nào đó phải được giải quyết trước. Nếu thiếu <Suspense>, mỗi trong số chúng sẽ cần xử lý trạng thái đang tải / lỗi và đã tải của riêng mình. Trong trường hợp tồi nhất, chúng ta có thể thấy ba biểu tượng tải trên trang, với nội dung hiển thị vào thời điểm khác nhau.
Thành phần <Suspense> cho phép chúng ta hiển thị trạng thái đang tải / lỗi cấp độ cao nhất trong khi chúng ta đợi các phụ thuộc async lồng nhau này được giải quyết.

Có hai loại phụ thuộc async mà <Suspense> có thể đợi:

  1. Các thành phần với một hook setup() bất đồng bộ. Điều này bao gồm các thành phần sử dụng <script setup> với các biểu thức await ở cấp độ cao nhất.
  2. Thành phần Async.

1.1 async setup()

Hook setup() của một thành phần API Hợp thành có thể là async:

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

Nếu sử dụng <script setup>, sự hiện diện của các biểu thức await ở cấp độ cao nhất tự động làm cho thành phần trở thành một phụ thuộc bất đồng bộ:

<script setup>
const res = await fetch(...)
const posts = await res.json()
</script>
<template>
  {{ posts }}
</template>

1.2 Thành phần Async

Thành phần Async mặc định là có thể treo. Điều này có nghĩa là nếu nó có một <Suspense> trong chuỗi cha, nó sẽ được xem như là một phụ thuộc async của <Suspense> đó. Trong trường hợp này, trạng thái đang tải sẽ được điều khiển bởi <Suspense>, và các tùy chọn tải, lỗi, độ trễ và thời gian chờ của thành phần sẽ bị bỏ qua.

Thành phần async có thể từ chối kiểm soát Suspense và cho phép thành phần luôn kiểm soát trạng thái tải của riêng mình bằng cách chỉ định suspensible: false trong các tùy chọn của nó.

2. Trạng thái Đang tải

Thành phần <Suspense> có hai khe: #default#fallback. Cả hai khe chỉ cho phép một nút con ngay lập tức. Nút trong khe mặc định được hiển thị nếu có thể. Nếu không, nút trong khe fallback sẽ được hiển thị thay vào đó.

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

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

Trên kết xuất ban đầu, <Suspense> sẽ kết xuất nội dung khe mặc định trong bộ nhớ. Nếu gặp bất kỳ phụ thuộc async nào trong quá trình này, nó sẽ chuyển sang trạng thái đang chờ. Trong trạng thái đang chờ, nội dung fallback sẽ được hiển thị. Khi tất cả các phụ thuộc async gặp phải đã được giải quyết, chuyển sang trạng thái đã giải quyết và nội dung khe mặc định đã được giải quyết sẽ được hiển thị.
Nếu không có phụ thuộc async nào được gặp phải trong quá trình kết xuất ban đầu, <Suspense> sẽ trực tiếp chuyển sang trạng thái đã giải quyết.

Một khi ở trong trạng thái đã giải quyết, <Suspense> chỉ sẽ quay lại trạng thái đang chờ nếu nút gốc của khe #default bị thay thế. Các phụ thuộc async mới lồng sâu hơn trong cây sẽ không làm cho <Suspense> quay lại trạng thái đang chờ.

Khi có một sự quay lại xảy ra, nội dung fallback sẽ không được hiển thị ngay lập tức. Thay vào đó, <Suspense> sẽ hiển thị nội dung #default trước đó trong khi chờ nội dung mới và các phụ thuộc async của nó được giải quyết. Hành vi này có thể được cấu hình với thuộc tính timeout: <Suspense> sẽ chuyển sang nội dung fallback nếu mất hơn timeout để kết xuất nội dung mặc định mới. Một giá trị timeout của 0 sẽ làm cho nội dung fallback được hiển thị ngay lập tức khi nội dung mặc định được thay thế.

3. Sự kiện

Thành phần <Suspense> phát ra 3 sự kiện: pending, resolvefallback. Sự kiện pending xảy ra khi chuyển sang trạng thái đang chờ. Sự kiện resolve được phát ra khi nội dung mới đã hoàn tất quá trình giải quyết trong khe default. Sự kiện fallback được kích hoạt khi nội dung của khe fallback được hiển thị.
Các sự kiện có thể được sử dụng, ví dụ, để hiển thị một chỉ báo đang tải trước mặt DOM cũ trong khi các thành phần mới đang tải.

4. Xử lý Lỗi

<Suspense> hiện tại không cung cấp xử lý lỗi thông qua chính thành phần – tuy nhiên, bạn có thể sử dụng tùy chọn errorCaptured hoặc hook onErrorCaptured() để bắt và xử lý lỗi async trong thành phần cha của <Suspense>.

5. Kết hợp với Các Thành Phần Khác

Thông thường muốn sử dụng <Suspense> kết hợp với các thành phần <KeepAlive>  và <Transition> . Thứ tự lồng nhau của các thành phần này quan trọng để có tất cả chúng hoạt động đúng cách.

Ngoài ra, các thành phần này thường được sử dụng kết hợp với thành phần từ Vue Router.

Ví dụ sau cho thấy cách lồng nhau các thành phần này để tất cả chúng hoạt động như mong đợi. Đối với các kết hợp đơn giản hơn, bạn có thể loại bỏ các thành phần mà bạn không cần:

<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...
        </Suspense>
      </KeepAlive>
    </Transition>
</RouterView>

Vue Router có hỗ trợ tích hợp sẵn cho các thành phần tải một cách lười biếng bằng cách sử dụng các nhập động. Chúng khác biệt so với các thành phần async và hiện tại chúng sẽ không kích hoạt <Suspense> . Tuy nhiên, chúng vẫn có thể có các thành phần async làm con và những thành phần đó có thể kích hoạt <Suspense> theo cách thông thường.

6. Suspense Lồng Nhau

Khi chúng ta có nhiều thành phần async (phổ biến cho các thành phần lồng nhau hoặc dựa trên bố cục như thế này:

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

<Suspense> tạo ra một ranh giới sẽ giải quyết tất cả các thành phần async xuống cây, như mong đợi. Tuy nhiên, khi chúng ta thay đổi DynamicAsyncOuter, <Suspense> đợi nó đúng cách, nhưng khi chúng ta thay đổi DynamicAsyncInner, thành phần DynamicAsyncInner lồng nhau hiển thị một nút trống cho đến khi nó được giải quyết (thay vì nút trước đó hoặc khe fallback).
Để giải quyết vấn đề đó, chúng ta có thể có một suspense lồng nhau để xử lý bản vá cho thành phần lồng nhau, như sau:

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

Nếu bạn không thiết lập prop suspensible, <Suspense> lồng trong sẽ được xem xét như một thành phần đồng bộ bởi <Suspense> cha. Điều đó có nghĩa là nó có khe fallback riêng của mình và nếu cả hai thành phần Dynamic thay đổi cùng một lúc, có thể có các nút trống và nhiều chu kỳ bản vá trong khi <Suspense> con đang tải cây phụ thuộc của chính nó, điều này có thể không mong muốn. Khi được thiết lập, toàn bộ xử lý phụ thuộc async được giao cho <Suspense> cha (bao gồm các sự kiện được phát ra) và <Suspense> lồng bên trong chỉ phục vụ như một ranh giới khác để giải quyết phụ thuộc và bản vá.

Chúng ta đã khám phá sâu hơn về Vuejs with Suspense thông qua bài viết này trên Cafedev. Hy vọng rằng những thông tin và kiến thức bạn nhận được sẽ giúp bạn hiểu rõ hơn về tính năng này và áp dụng nó vào dự án của mình. Nếu bạn muốn tiếp tục theo dõi các bài viết mới nhất về công nghệ và phát triển phần mềm, đừng quên trở lại với Cafedev thường xuyên. Cảm ơn bạn đã đồng hành cùng chúng tôi trên Cafedev!

Tham khảo thêm: MIỄN PHÍ 100% | Series tự học Vuejs từ cơ bản tới nâng cao

Các nguồn kiến thức MIỄN PHÍ VÔ GIÁ từ cafedev tại đây

Nếu bạn thấy hay và hữu ích, bạn có thể tham gia các kênh sau của CafeDev để nhận được nhiều hơn nữa:

Chào thân ái và quyết thắng!

Đăng ký kênh youtube để ủng hộ Cafedev nha các bạn, Thanks you!