卧槽,牛逼!vue3的組件竟然還能“暫停”渲染!

来源:https://www.cnblogs.com/heavenYJJ/p/18366122
-Advertisement-
Play Games

有沒有一種完美的方案,從服務端獲取數據的邏輯放在子組件中,並且在獲取數據的期間讓子組件“暫停”一下,先不去渲染,等到數據請求完成後再第一次去渲染子組件呢? ...


前言

有的時候我們想要從服務端拿到數據後再去渲染一個組件,為了實現這個效果我們目前有幾種實現方式:

  • 將數據請求放到父組件去做,並且使用v-if控制拿到子組件後才去渲染子組件,然後將數據從父組件通過props傳給子組件。

  • 在子組件的onMounted中請求數據,並且使用v-if在子組件的template最外層進行控制,只有拿到數據後才渲染子組件中的內容。

上面這兩種方案都有各自的缺點,不夠完美。最理想的方案是將從服務端獲取數據的邏輯放在子組件中,並且在獲取數據的期間讓子組件“暫停”一下,先不去渲染,等到數據請求完成後再第一次去渲染子組件。

歐陽寫了一本開源電子書vue3編譯原理揭秘,看完這本書可以讓你對vue編譯的認知有質的提升。這本書初、中級前端能看懂,完全免費,只求一個star。

完美的解決方案

第一種方法的缺點是:子組件雖然拿到數據後才開始渲染,但是數據請求的邏輯卻放到了父組件上面,我們期望所有的邏輯都封裝在子組件內部。

第二種方法的缺點是:實際上是初始化時就渲染了一次子組件,此時我們還沒從服務端拿到數據。所以不得不使用v-iftemplate的最外層控制,此時不渲染子組件中的內容。當從服務端拿到數據後再第二次渲染子組件,此時才將子組件中的內容渲染到頁面上。這種方法明顯子組件渲染了2次。

那麼有沒有一種完美的方案,從服務端獲取數據的邏輯放在子組件中,並且在獲取數據的期間讓子組件“暫停”一下,先不去渲染,等到數據請求完成後再第一次去渲染子組件呢?

答案是:當然可以,vue3的Suspense組件+在setup頂層使用await獲取數據就能完美的實現這個需求!!!

兩個不完美的例子

為了讓你更直觀的看到完美方案的牛逼,我們先來看看前面講的兩個不夠完美的例子。

父組件中請求數據的例子

下麵這個是父組件中請求數據的例子,父組件的代碼如下:

<template>
  <ChildDemo v-if="user" :user="user" />
  <div v-else>
    <p>loading...</p>
  </div>
</template>

<script setup lang="ts">
import { ref, onMounted } from "vue";
import ChildDemo from "./Child.vue";

const user = ref(null);

async function fetchUser() {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve({
        name: "張三",
        phone: "13800138000",
      });
    }, 2000);
  });
}

onMounted(async () => {
  user.value = await fetchUser();
});
</script>

子組件的代碼如下:

<template>
  <div>
    <p>用戶名:{{ user.name }}</p>
    <p>手機號:{{ user.phone }}</p>
  </div>
</template>

<script setup lang="ts">
const props = defineProps(["user"]);
</script>

這種方案我們將從服務端獲取user的邏輯全部放到了父組件中,並且使用propsuser傳遞給子組件,並且在從服務端獲取數據的期間顯示一個loading的文案。

這樣雖然實現了我們的需求但是將子組件獲取user的邏輯放到了父組件中,我們期望將這些邏輯全部封裝在子組件中,所以這個方案並不完美。

子組件在onMounted中請求數據的例子

我們來看看第二種方案,父組件代碼代碼如下:

<template>
  <ChildDemo />
</template>

<script setup lang="ts">
import ChildDemo from "./Child.vue";
</script>

子組件代碼如下:

<template>
  <div v-if="user">
    <p>用戶名:{{ user.name }}</p>
    <p>手機號:{{ user.phone }}</p>
  </div>
  <div v-else>
    <p>loading...</p>
  </div>
</template>

<script setup lang="ts">
import { ref, onMounted } from "vue";

const user = ref(null);

async function fetchUser() {
  // 使用setTimeout模擬從服務端獲取數據
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve({
        name: "張三",
        phone: "13800138000",
      });
    }, 2000);
  });
}

onMounted(async () => {
  user.value = await fetchUser();
});
</script>

我們將數據請求放在了onMounted中,初始化時會去第一次渲染子組件。此時user的值還是null,所以我們不得不在template的最外層使用v-if="user"控制此時不顯示子組件的內容,在v-else中去渲染loading文案。

當從服務端拿到數據後給響應式變數user重新賦值,會觸發頁面重新渲染,此時會進行第二次渲染才將子組件的內容渲染到頁面上。

從上面可以看到這種方案子組件明顯渲染了兩次,並且我們還將loading的顯示邏輯寫在子組件的內部,增加了子組件代碼的複雜度。所以這種方案也並不完美。

最完美的方案就是在fetchUser期間讓子組件“暫停”渲染fallback去渲染一個loading頁面。並且這個loading的顯示邏輯不需要封裝在子組件中,在“暫停”渲染期間自動就能顯示出來。等到從服務端請求數據完成後才開始渲染子組件,並且自動的卸載掉loading頁面。

Suspense + await實現完美的例子

下麵這個是官網對Suspense的介紹:

<Suspense> 是一個內置組件,用來在組件樹中協調對非同步依賴的處理。它讓我們可以在組件樹上層等待下層的多個嵌套非同步依賴項解析完成,並可以在等待時渲染一個載入狀態。

上面的意思是Suspense組件能夠監聽下麵的非同步子組件,在等待非同步子組件完成渲染之前,可以去渲染一個loading的頁面。

Suspense組件支持兩個插槽:#default 和 #fallback。如果#default插槽中有非同步組件,那麼就會先去渲染 #fallback中的內容,等到非同步組件載入完成後就會將#fallback中的內容給幹掉,改為將非同步組件的內容渲染到頁面上。

如果我們的子組件是一個非同步組件,那麼Suspense不就可以幫我們實現想要的功能吖。

Suspense可以在非同步子組件的載入過程中使用 #fallback插槽自動幫我們渲染一個載入中的loading,等到非同步子組件載入完成後才會第一次去渲染子組件中的內容。

那麼現在的問題是如何將我們的子組件變成非同步子組件?

這個問題的答案其實vue官網就已經告訴我們了,如果一個組件的<script setup>頂層使用了await,那麼這個組件就會變成一個非同步組件。我們接下來只需要在子組件的頂層使用await去請求服務端數據就可以啦。

完美方案的父組件

下麵這個是使用Suspense改造後的父組件代碼,如下:

<template>
  <Suspense>
    <AsyncChildDemo />
    <template #fallback>loading...</template>
  </Suspense>
</template>

<script setup lang="ts">
import AsyncChildDemo from "./AsyncChild.vue";
</script>

在父組件中使用了Suspense組件,給這個組件傳了2個插槽。#default插槽為非同步子組件AsyncChildDemo,預設插槽可以不用給元素上面添加#default

並且使用了#fallback插槽,在非同步子組件載入過程中會暫時先不去渲染非同步子組件AsyncChildDemo。改為先渲染#fallback插槽中的loading,等到非同步子組件載入完成後會自動將loading替換為子組件中的內容。

完美方案的子組件

下麵這個是使用了await改造後的子組件代碼,如下:

<template>
  <div>
    <p>用戶名:{{ user.name }}</p>
    <p>手機號:{{ user.phone }}</p>
  </div>
</template>

<script setup lang="ts">
import { ref } from "vue";

const user = ref(null);
user.value = await fetchUser();

async function fetchUser() {
  // 使用setTimeout模擬從服務端獲取數據
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve({
        name: "張三",
        phone: "13800138000",
      });
    }, 2000);
  });
}
</script>

我們在<script setup>頂層中使用了await,然後將await拿到的值賦值給user變數。在頂層使用了await後子組件就變成了一個非同步組件,等到await fetchUser()執行完了後,也就是從服務端拿到了數據後,子組件才算是載入完成了。

並且由於我們在父組件中使用了Suspense,所以在子組件載入完成之前,也就是從服務端拿到數據之前,都不會去渲染子組件(相當於“暫停”渲染子組件)。而是去渲染#fallback插槽中的loading,等到從服務端拿到數據之後非同步子組件才算是載入完成了。此時才會第一次去渲染子組件,並且將loading替換為子組件渲染的內容。

因為第一次渲染子組件時已經從服務端拿到了user的值,此時user已經不是null了,所以我們可以不用在template的最上層使用v-if="user",儘管在template中有去讀user.name

經過父組件Suspense + 子組件頂層await的改造後,在渲染父組件的Suspense時發現他的子組件有非同步組件,就會“暫停”渲染子組件,改為自動渲染loading組件。

子組件在setup頂層使用await等待從服務端請求數據,當從服務端拿到了數據後此時子組件才算是載入完成,此時才會進行第一次渲染,並且自動將loading中的內容替換為子組件中渲染的內容。

並且在Suspense中還支持多個非同步子組件分別從服務端獲取數據,等這幾個非同步子組件都從服務端獲取到數據後才會自動的將loading替換為這幾個非同步子組件渲染的內容。

還有就是Suspense組件目前依然還是實驗性的功能,生產環境使用需要謹慎。

簡單看看Suspense如何實現“暫停”渲染?

Suspense在渲染子組件時,發現子組件是一個非同步組件就不會立即執行非同步子組件的render函數。而是會加一個名為deps的標記,標明當前預設子組件是一個非同步組件,暫停渲染非同步子組件。

由於非同步子組件是一個Promise,所以可以在載入非同步子組件的Promise後添加.then()方法,在.then()方法中才會去繼續渲染非同步子組件。

目前非同步子組件已經暫停渲染了,接著就是會去讀取deps標記。如果deps標記為true,說明非同步子組件暫停渲染了,此時就會去將fallback插槽中的loading組件渲染到頁面上。

當非同步子組件載入完成後就會觸發Promise.then()方法,從而繼續渲染非同步子組件。在.then()方法中會去執行非同步子組件的render函數去生成虛擬DOM,然後根據虛擬DOM生成真實DOM。最後就是將原本頁面上渲染的fallback插槽中的內容替換為非同步組件生成的真實DOM中的內容。

下麵這個是我畫的流程圖(流程圖後面還有文末總結):
full-progress

總結

這篇文章我們講了有的場景需要從服務端拿到數據後再去渲染一個組件,此時我們就可以使用父組件Suspense + 子組件頂層await的完美方案。

在渲染父組件的Suspense組件時發現他的子組件有非同步組件,就會“暫停”渲染子組件,改為自動渲染loading組件。

子組件在setup頂層使用await等待從服務端請求數據,當從服務端拿到了數據後此時子組件才算是載入完成,此時才會進行第一次渲染,並且自動將loading中的內容替換為子組件中渲染的內容。

並且在Suspense中還支持多個非同步子組件分別從服務端獲取數據,等這幾個非同步子組件都從服務端獲取到數據後才會自動的將loading替換為這幾個非同步子組件渲染的內容。

最後就是Suspense組件目前依然還是實驗性的功能,生產環境使用需要謹慎。

關註公眾號:【前端歐陽】,給自己一個進階vue的機會

另外歐陽寫了一本開源電子書vue3編譯原理揭秘,看完這本書可以讓你對vue編譯的認知有質的提升。這本書初、中級前端能看懂,完全免費,只求一個star。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 在數據技術不斷演進的背景下,雲資料庫的崛起和雲原生資料庫的普及標志著資料庫技術的顯著變革。從最初的自建資料庫模式到如今的雲原生資料庫,企業在數據管理上的選擇變得更加豐富和靈活。雲資料庫不僅僅是對傳統資料庫技術的一個遷移,更是對其進行了一次全面的升級和優化。 ...
  • ElasticSearch服務提供對索引操作、文檔操作、分詞操作等多種介面。介面的查詢通常採用DSL的形式,也可採用SQL形式進行查詢。 ...
  • 原文: Jetpack架構組件學習(5)——Hilt 註入框架使用-Stars-One的雜貨小窩 本篇需要有Kotlin基礎知識,否則可能閱讀本篇會有所困難! 介紹說明 實際上,郭霖那篇文章已經講得比較明白了(具體參考鏈接都貼在下文了),這裡簡單總結下: 如果按照之前我們的MVC寫法,我們可以直接在 ...
  • ​IT寒冬之下,程式員這個職業不再像以往那麼吃香,尤其是APP開發的門檻越來越高,使得安卓程式員不得不求變,如果不在技術上及時轉型提高,逆水行舟未來不可期呀。 有鑒於此,博主整理了幾個可供安卓程式員的技術轉型發展方向,供大家參考。 1、繼續深耕Android的應用開發 谷歌爸爸是安卓的爹,要想繼續吃 ...
  • 以下為個人理解,如錯請評 CE: 憑據加密 (CE) 存儲空間, 實際路徑/data/user_ce/ DE: 設備加密 (DE) 存儲空間, 實際路徑/data/user_de/ 系統解鎖前也能夠運行一些App,但是需要App在manifest里顯式聲明android:directBootAwar ...
  • 原文: Jetpack Compose學習(13)——Compose生命周期及副作用函數-Stars-One的雜貨小窩 此文建議需要瞭解kotlin的lambda表達式使用和協程基礎使用,不然可能會有些閱讀困難 本篇算是參考他人文章,按照自己理解重新總結了下吧,偏理論 生命周期 Composable ...
  • 在實現用戶協議彈窗時,通常我們會想到使用系統自定義彈窗,併在彈窗中點擊跳轉到Web頁面。但在HarmonyOS中,由於系統彈窗的顯示優先順序高於其他組件,即使跳轉到Web頁面,彈窗依然會顯示在最上層。 為瞭解決這個問題,我們可以自定義一個組件來模擬彈窗,這樣當跳轉到Web頁面時,Web內容會覆蓋這個模 ...
  • title: 使用 preloadRouteComponents 提升 Nuxt 應用的性能 date: 2024/8/19 updated: 2024/8/19 author: cmdragon excerpt: preloadRouteComponents 是提升 Nuxt 應用性能的一個簡單而 ...
一周排行
    -Advertisement-
    Play Games
  • 前言 推薦一款基於.NET 8、WPF、Prism.DryIoc、MVVM設計模式、Blazor以及MySQL資料庫構建的企業級工作流系統的WPF客戶端框架-AIStudio.Wpf.AClient 6.0。 項目介紹 框架採用了 Prism 框架來實現 MVVM 模式,不僅簡化了 MVVM 的典型 ...
  • 先看一下效果吧: 我們直接通過改造一下原版的TreeView來實現上面這個效果 我們先創建一個普通的TreeView 代碼很簡單: <TreeView> <TreeViewItem Header="人事部"/> <TreeViewItem Header="技術部"> <TreeViewItem He ...
  • 1. 生成式 AI 簡介 https://imp.i384100.net/LXYmq3 2. Python 語言 https://imp.i384100.net/5gmXXo 3. 統計和 R https://youtu.be/ANMuuq502rE?si=hw9GT6JVzMhRvBbF 4. 數 ...
  • 本文為大家介紹下.NET解壓/壓縮zip文件。雖然解壓縮不是啥核心技術,但壓縮性能以及進度處理還是需要關註下,針對使用較多的zip開源組件驗證,給大家提供個技術選型參考 之前在《.NET WebSocket高併發通信阻塞問題 - 唐宋元明清2188 - 博客園 (cnblogs.com)》講過,團隊 ...
  • 之前寫過兩篇關於Roslyn源生成器生成源代碼的用例,今天使用Roslyn的代碼修複器CodeFixProvider實現一個cs文件頭部註釋的功能, 代碼修複器會同時涉及到CodeFixProvider和DiagnosticAnalyzer, 實現FileHeaderAnalyzer 首先我們知道修 ...
  • 在軟體行業,經常會聽到一句話“文不如表,表不如圖”說明瞭圖形在軟體應用中的重要性。同樣在WPF開發中,為了程式美觀或者業務需要,經常會用到各種個樣的圖形。今天以一些簡單的小例子,簡述WPF開發中幾何圖形(Geometry)相關內容,僅供學習分享使用,如有不足之處,還請指正。 ...
  • 在 C# 中使用 RabbitMQ 通過簡訊發送重置後的密碼到用戶的手機號上,你可以按照以下步驟進行 1.安裝 RabbitMQ 客戶端庫 首先,確保你已經安裝了 RabbitMQ 客戶端庫。你可以通過 NuGet 包管理器來安裝: dotnet add package RabbitMQ.Clien ...
  • 1.下載 Protocol Buffers 編譯器(protoc) 前往 Protocol Buffers GitHub Releases 頁面。在 "Assets" 下找到適合您系統的壓縮文件,通常為 protoc-{version}-win32.zip 或 protoc-{version}-wi ...
  • 簡介 在現代微服務架構中,服務發現(Service Discovery)是一項關鍵功能。它允許微服務動態地找到彼此,而無需依賴硬編碼的地址。以前如果你搜 .NET Service Discovery,大概率會搜到一大堆 Eureka,Consul 等的文章。現在微軟為我們帶來了一個官方的包:Micr ...
  • ZY樹洞 前言 ZY樹洞是一個基於.NET Core開發的簡單的評論系統,主要用於大家分享自己心中的感悟、經驗、心得、想法等。 好了,不賣關子了,這個項目其實是上班無聊的時候寫的,為什麼要寫這個項目呢?因為我單純的想吐槽一下工作中的不滿而已。 項目介紹 項目很簡單,主要功能就是提供一個簡單的評論系統 ...