給我5分鐘,保證教會你在vue3中動態載入遠程組件

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

前言 在一些特殊的場景中(比如低代碼、減少小程式包體積、類似於APP的熱更新),我們需要從服務端動態載入.vue文件,然後將動態載入的遠程vue組件渲染到我們的項目中。今天這篇文章我將帶你學會,在vue3中如何去動態載入遠程組件。 歐陽寫了一本開源電子書vue3編譯原理揭秘,這本書初中級前端能看懂。 ...


前言

在一些特殊的場景中(比如低代碼、減少小程式包體積、類似於APP的熱更新),我們需要從服務端動態載入.vue文件,然後將動態載入的遠程vue組件渲染到我們的項目中。今天這篇文章我將帶你學會,在vue3中如何去動態載入遠程組件。

歐陽寫了一本開源電子書vue3編譯原理揭秘,這本書初中級前端能看懂。完全免費,只求一個star。

defineAsyncComponent非同步組件

想必聰明的你第一時間就想到了defineAsyncComponent方法。我們先來看看官方對defineAsyncComponent方法的解釋:

定義一個非同步組件,它在運行時是懶載入的。參數可以是一個非同步載入函數,或是對載入行為進行更具體定製的一個選項對象。

defineAsyncComponent方法的返回值是一個非同步組件,我們可以像普通組件一樣直接在template中使用。和普通組件的區別是,只有當渲染到非同步組件時才會調用載入內部實際組件的函數。

我們先來簡單看看使用defineAsyncComponent方法的例子,代碼如下:

import { defineAsyncComponent } from 'vue'

const AsyncComp = defineAsyncComponent(() => {
  return new Promise((resolve, reject) => {
    // ...從伺服器獲取組件
    resolve(/* 獲取到的組件 */)
  })
})
// ... 像使用其他一般組件一樣使用 `AsyncComp`

defineAsyncComponent方法接收一個返回 Promise 的回調函數,在Promise中我們可以從服務端獲取vue組件的code代碼字元串。然後使用resolve(/* 獲取到的組件 */)將拿到的組件傳給defineAsyncComponent方法內部處理,最後和普通組件一樣在template中使用AsyncComp組件。

從服務端獲取遠程組件

有了defineAsyncComponent方法後事情從錶面上看著就很簡單了,我們只需要寫個方法從服務端拿到vue文件的code代碼字元串,然後在defineAsyncComponent方法中使用resolve拿到的vue組件。

第一步就是本地起一個伺服器,使用伺服器返回我們的vue組件。這裡我使用的是http-server,安裝也很簡單:

npm install http-server -g

使用上面的命令就可以全局安裝一個http伺服器了。

接著我在項目的public目錄下新建一個名為remote-component.vue的文件,這個vue文件就是我們想從服務端載入的遠程組件。remote-component.vue文件中的代碼如下:

<template>
  <p>我是遠程組件</p>
  <p>
    當前遠程組件count值為:<span class="count">{{ count }}</span>
  </p>
  <button @click="count++">點擊增加遠程組件count</button>
</template>

<script setup>
import { ref } from "vue";
const count = ref(0);
</script>

<style>
.count {
  color: red;
}
</style>

從上面的代碼可以看到遠程vue組件和我們平時寫的vue代碼沒什麼區別,有templateref響應式變數、style樣式。

接著就是在終端執行http-server ./public --cors命令啟動一個本地伺服器,伺服器預設埠為8080。但是由於我們本地起的vite項目預設埠為5173,所以為了避免跨域這裡需要加--cors ./public的意思是指定當前目錄的public文件夾。

啟動了一個本地伺服器後,我們就可以使用 http://localhost:8080/remote-component.vue鏈接從服務端訪問遠程組件啦,如下圖:
remote-component

從上圖中可以看到在瀏覽器中訪問這個鏈接時觸發了下載遠程vue組件的操作。

defineAsyncComponent載入遠程組件

const RemoteChild = defineAsyncComponent(async () => {
  return new Promise(async (resolve) => {
    const res = await fetch("http://localhost:8080/remote-component.vue");
    const code = await res.text();
    console.log("code", code);
    resolve(code);
  });
});

接下來我們就是在defineAsyncComponent方法接收的 Promise 的回調函數中使用fetch從服務端拿到遠程組件的code代碼字元串應該就行啦,代碼如下:

同時使用console.log("code", code)打個日誌看一下從服務端過來的vue代碼。

上面的代碼看著已經完美實現動態載入遠程組件了,結果不出意外在瀏覽器中運行時報錯了。如下圖:
error

在上圖中可以看到從服務端拿到的遠程組件的代碼和我們的remote-component.vue的源代碼是一樣的,但是為什麼會報錯呢?

這裡的報錯信息顯示載入非同步組件報錯,還記得我們前面說過的defineAsyncComponent方法是在回調中resolve(/* 獲取到的組件 */)。而我們這裡拿到的code是一個組件嗎?

我們這裡拿到的code只是組件的源代碼,也就是常見的單文件組件SFC。而defineAsyncComponent中需要的是由源代碼編譯後拿的的vue組件對象,我們將組件源代碼丟給defineAsyncComponent當然會報錯了。

看到這裡有的小伙伴有疑問了,我們平時在父組件中import子組件不是也一樣在template就直接使用了嗎?

子組件local-child.vue代碼:

<template>
  <p>我是本地組件</p>
  <p>
    當前本地組件count值為:<span class="count">{{ count }}</span>
  </p>
  <button @click="count++">點擊增加本地組件count</button>
</template>

<script setup>
import { ref } from "vue";
const count = ref(0);
</script>

<style>
.count {
  color: red;
}
</style>

父組件代碼:

<template>
  <LocalChild />
</template>

<script setup lang="ts">
import LocalChild from "./local-child.vue";
console.log("LocalChild", LocalChild);
</script>

上面的import導入子組件的代碼寫了這麼多年你不覺得怪怪的嗎?

按照常理來說要import導入子組件,那麼在子組件裡面肯定要寫export才可以,但是在子組件local-child.vue中我們沒有寫任何關於export的代碼。

答案是在父組件import導入子組件觸發了vue-loader或者@vitejs/plugin-vue插件的鉤子函數,在鉤子函數中會將我們的源代碼單文件組件SFC編譯成一個普通的js文件,在js文件中export default導出編譯後的vue組件對象。

這裡使用console.log("LocalChild", LocalChild)來看看經過編譯後的vue組件對象是什麼樣的,如下圖:
import-comp

從上圖可以看到經過編譯後的vue組件是一個對象,對象中有rendersetup等方法。defineAsyncComponent方法接收的組件就是這樣的vue組件對象,但是我們前面卻是將vue組件源碼丟給他,當然會報錯了。

最終解決方案vue3-sfc-loader

從服務端拿到遠程vue組件源碼後,我們需要一個工具將拿到的vue組件源碼編譯成vue組件對象。幸運的是優秀的vue不光暴露出一些常見的API,而且還將一些底層API給暴露了出來。比如在@vue/compiler-sfc包中就暴露出來了compileTemplatecompileScriptcompileStyleAsync等方法。

如果你看過我寫的 vue3編譯原理揭秘 開源電子書,你應該對這幾個方法覺得很熟悉。

  • compileTemplate方法:用於處理單文件組件SFC中的template模塊。

  • compileScript方法:用於處理單文件組件SFC中的script模塊。

  • compileStyleAsync方法:用於處理單文件組件SFC中的style模塊。

vue3-sfc-loader包的核心代碼就是調用@vue/compiler-sfc包的這些方法,將我們的vue組件源碼編譯為想要的vue組件對象。
下麵這個是改為使用vue3-sfc-loader包後的代碼,如下:

import * as Vue from "vue";
import { loadModule } from "vue3-sfc-loader";

const options = {
  moduleCache: {
    vue: Vue,
  },
  async getFile(url) {
    const res = await fetch(url);
    const code = await res.text();
    return code;
  },
  addStyle(textContent) {
    const style = Object.assign(document.createElement("style"), {
      textContent,
    });
    const ref = document.head.getElementsByTagName("style")[0] || null;
    document.head.insertBefore(style, ref);
  },
};

const RemoteChild = defineAsyncComponent(async () => {
  const res = await loadModule(
    "http://localhost:8080/remote-component.vue",
    options
  );
  console.log("res", res);
  return res;
});

loadModule函數接收的第一個參數為遠程組件的URL,第二個參數為options。在options中有個getFile方法,獲取遠程組件的code代碼字元串就是在這裡去實現的。

我們在終端來看看經過loadModule函數處理後拿到的vue組件對象是什麼樣的,如下圖:
compiler-remote-comp

從上圖中可以看到經過loadModule函數的處理後就拿到來vue組件對象啦,並且這個組件對象上面也有熟悉的render函數和setup函數。其中render函數是由遠程組件的template模塊編譯而來的,setup函數是由遠程組件的script模塊編譯而來的。

看到這裡你可能有疑問,遠程組件的style模塊怎麼沒有在生成的vue組件對象上面有提現呢?

答案是style模塊編譯成的css不會塞到vue組件對象上面去,而是單獨通過options上面的addStyle方法傳回給我們了。addStyle方法接收的參數textContent的值就是style模塊編譯而來css字元串,在addStyle方法中我們是創建了一個style標簽,然後將得到的css字元串插入到頁面中。

完整父組件代碼如下:

<template>
  <LocalChild />
  <div class="divider" />
  <button @click="showRemoteChild = true">載入遠程組件</button>
  <RemoteChild v-if="showRemoteChild" />
</template>

<script setup lang="ts">
import { defineAsyncComponent, ref, onMounted } from "vue";
import * as Vue from "vue";
import { loadModule } from "vue3-sfc-loader";
import LocalChild from "./local-child.vue";

const showRemoteChild = ref(false);

const options = {
  moduleCache: {
    vue: Vue,
  },
  async getFile(url) {
    const res = await fetch(url);
    const code = await res.text();
    return code;
  },
  addStyle(textContent) {
    const style = Object.assign(document.createElement("style"), {
      textContent,
    });
    const ref = document.head.getElementsByTagName("style")[0] || null;
    document.head.insertBefore(style, ref);
  },
};

const RemoteChild = defineAsyncComponent(async () => {
  const res = await loadModule(
    "http://localhost:8080/remote-component.vue",
    options
  );
  console.log("res", res);
  return res;
});
</script>

<style scoped>
.divider {
  background-color: red;
  width: 100vw;
  height: 1px;
  margin: 20px 0;
}
</style>

在上面的完整例子中,首先渲染了本地組件LocalChild。然後當點擊“載入遠程組件”按鈕後再去渲染遠程組件RemoteChild。我們來看看執行效果,如下圖:
full

從上面的gif圖中可以看到,當我們點擊“載入遠程組件”按鈕後,在network中才去載入了遠程組件remote-component.vue。並且將遠程組件渲染到了頁面上後,通過按鈕的點擊事件可以看到遠程組件的響應式依然有效。

vue3-sfc-loader同時也支持在遠程組件中去引用子組件,你只需在options額外配置一個pathResolve就行啦。pathResolve方法配置如下:

const options = {
  pathResolve({ refPath, relPath }, options) {
    if (relPath === ".")
      // self
      return refPath;

    // relPath is a module name ?
    if (relPath[0] !== "." && relPath[0] !== "/") return relPath;

    return String(
      new URL(relPath, refPath === undefined ? window.location : refPath)
    );
  },
  // getFile方法
  // addStyle方法
}

其實vue3-sfc-loader包的核心代碼就300行左右,主要就是調用vue暴露出來的一些底層API。如下圖:
vue3-sfc-loader

總結

這篇文章講了在vue3中如何從服務端載入遠程組件,首先我們需要使用defineAsyncComponent方法定義一個非同步組件,這個非同步組件是可以直接在template中像普通組件一樣使用。

但是由於defineAsyncComponent接收的組件必須是編譯後的vue組件對象,而我們從服務端拿到的遠程組件就是一個普通的vue文件,所以這時我們引入了vue3-sfc-loader包。vue3-sfc-loader包的作用就是在運行時將一個vue文件編譯成vue組件對象,這樣我們就可以實現從服務端載入遠程組件了。

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

另外歐陽寫了一本開源電子書vue3編譯原理揭秘,這本書初中級前端能看懂。完全免費,只求一個star。


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

-Advertisement-
Play Games
更多相關文章
  • title: 使用 Nuxt 3 的 defineRouteRules 進行頁面級別的混合渲染 date: 2024/8/12 updated: 2024/8/12 author: cmdragon excerpt: 摘要:本文介紹了Nuxt 3中的defineRouteRules功能,用於實現頁面 ...
  • title: 掌握 Nuxt 3 的頁面元數據:使用 definePageMeta 進行自定義配置 date: 2024/8/11 updated: 2024/8/11 author: cmdragon excerpt: 摘要:本文詳細介紹Nuxt 3框架中definePageMeta的使用方法,包 ...
  • title: 使用 defineNuxtRouteMiddleware 創建路由中間件 date: 2024/8/10 updated: 2024/8/10 author: cmdragon excerpt: 本篇文章介紹瞭如何使用 defineNuxtRouteMiddleware 創建和應用路由 ...
  • 摘要:目前 OpenTiny HUICharts 已經成功落地在華為內部100多個產品中,持續提升了用戶的可視化體驗。 本文分享自華為雲社區《OpenTiny HUICharts 正式開源發佈,一個簡單、易上手的圖表組件庫》,作者: OpenTiny。 引言 大家好! 我們非常高興地跟大家宣佈,今天 ...
  • title: 使用 defineNuxtComponent`定義 Vue 組件 date: 2024/8/9 updated: 2024/8/9 author: cmdragon excerpt: 摘要:本文介紹了在Nuxt 3中使用defineNuxtComponent輔助函數定義類型安全的Vue ...
  • 我們是袋鼠雲數棧 UED 團隊,致力於打造優秀的一站式數據中台產品。我們始終保持工匠精神,探索前端道路,為社區積累並傳播經驗價值。 本文作者:佳嵐 可編輯表格在數棧產品中是一種比較常見的表單數據交互方式,一般都支持動態的新增、刪除、排序等基礎功能。 交互分類 可編輯表格一般為兩種交互形式: 實時保存 ...
  • title: 使用 createError 創建錯誤對象的詳細指南 date: 2024/8/8 updated: 2024/8/8 author: cmdragon excerpt: 摘要:本文介紹了createError函數在Nuxt應用開發中的使用方法,用於創建帶有附加元數據的錯誤對象,以提升 ...
  • title: 清除 Nuxt 狀態緩存:clearNuxtState date: 2024/8/7 updated: 2024/8/7 author: cmdragon excerpt: 摘要:本文介紹了Nuxt.js框架中clearNuxtState方法的使用,該方法用於清除useState管理的 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...