卧槽,牛逼!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 MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...