在vue3中动态加载远程组件
liuian 2024-12-31 12:57 51 浏览
前言
在一些特殊的场景中(比如低代码、类似于APP的热更新),我们需要从服务端动态加载.vue文件,然后将动态加载的远程vue组件渲染到我们的项目中。今天这篇文章我将带你学会,在vue3中如何去动态加载远程组件。
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代码没什么区别,有template、ref响应式变量、style样式。
接着就是在终端执行http-server ./public --cors命令启动一个本地服务器,服务器默认端口为8080。但是由于我们本地起的vite项目默认端口为5173,所以为了避免跨域这里需要加--cors。 ./public的意思是指定当前目录的public文件夹。
启动了一个本地服务器后,我们就可以使用 http://localhost:8080/remote-component.vue链接从服务端访问远程组件啦,如下图:
从上图中可以看到在浏览器中访问这个链接时触发了下载远程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代码。
上面的代码看着已经完美实现动态加载远程组件了,结果不出意外在浏览器中运行时报错了。如下图:
在上图中可以看到从服务端拿到的远程组件的代码和我们的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组件对象是什么样的,如下图:
从上图可以看到经过编译后的vue组件是一个对象,对象中有render、setup等方法。defineAsyncComponent方法接收的组件就是这样的vue组件对象,但是我们前面却是将vue组件源码丢给他,当然会报错了。
最终解决方案vue3-sfc-loader
从服务端拿到远程vue组件源码后,我们需要一个工具将拿到的vue组件源码编译成vue组件对象。幸运的是优秀的vue不光暴露出一些常见的API,而且还将一些底层API给暴露了出来。比如在@vue/compiler-sfc包中就暴露出来了compileTemplate、compileScript、compileStyleAsync等方法。
如果你看过我写的 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组件对象是什么样的,如下图:
从上图中可以看到经过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。我们来看看执行效果,如下图:
从上面的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中如何从服务端加载远程组件,首先我们需要使用defineAsyncComponent方法定义一个异步组件,这个异步组件是可以直接在template中像普通组件一样使用。
但是由于defineAsyncComponent接收的组件必须是编译后的vue组件对象,而我们从服务端拿到的远程组件就是一个普通的vue文件,所以这时我们引入了vue3-sfc-loader包。vue3-sfc-loader包的作用就是在运行时将一个vue文件编译成vue组件对象,这样我们就可以实现从服务端加载远程组件了。
相关推荐
- toshiba硬盘(TOSHIBA硬盘tlc)
-
东芝移动硬盘a3好,性价比很高,传输速率高,稳定耐用,安全高效外壳是磨砂质感!USB3.0,即插即用采用NTFS格式,兼容Windwos10、Windwos8.1、Windwos7,格式化后可兼容M...
- 完整版xp系统下载(xp系统最新版本安装包)
-
2012年前的可以无压力安装XP系统,搜索:itellyou.cn这里有WINDOWS几乎所有的系统。windowsXP系统升级的具体操作步骤如下:1、首先我们将老毛桃装机工具下载到U盘,将老毛桃...
- ps下载电脑版官方下载(ps电脑版下载地址)
-
目前在电脑上免费下载PS是不太可能的。主要有以下几个原因。1.AdobePhotoshop(简称PS)是一款商业软件,它需要用户购买和激活许可证才能合法使用。从正规渠道下载并且获得合法授权需要付费...
- 迅猛兔加速器(迅猛兔加速器官网)
-
要下载迅猛兔加速器,首先需要在官网或其他可信的下载平台上搜索并找到该软件。一般情况下,官网提供的下载链接是最稳定和安全的选择。在下载之前,确保您的电脑或手机系统能够支持使用此软件,并检查下载链接的文件...
- 台式电脑怎么重做系统(台式电脑怎么重装系统)
-
你好,电脑系统重装的步骤如下:1.备份数据:在重装系统之前,需要备份电脑中的重要数据,以免数据丢失。2.准备安装介质:需要准备一个安装介质,可以是光盘、U盘或者硬盘分区镜像等。3.设置启动顺序:将电脑...
-
- 电脑无法从u盘启动怎么办(电脑无法从u盘启动解决方法)
-
电脑的进入不了u盘启动的解决方法:一、我们第一步需要确定的是你的u盘在别的电脑上检查一下U盘是否可读,如果可读的话是否成功制作了u盘启动盘了,因为想要启动进入pe的话需要u盘具备启动的功能。 二、如果你检查好自己的u盘已经成功制作了启动盘...
-
2026-01-13 10:05 liuian
- cpu频率越高越好吗(cpu频率越高速度越快吗)
-
高好。CPU的频率是影响CPU的一个重要因素,直观上来说,频率的高低影响了CPU的性能。频率越高,CPU性能越好;不过需要注意的是,CPU的主频表示在CPU内数字脉冲信号震荡的速度,与CPU实际的运算...
- 注册表清理软件(注册表清理软件残留软件)
-
你好!关于注册表清理工具的推荐,以下是几个值得推荐的工具:1.CCleaner:这是一款功能强大的免费清理工具,可以有效地清理注册表、垃圾文件等,使用简单方便。2.WiseRegistryCl...
- 显卡驱动升级有好处吗(显卡驱动升级有什么坏处)
-
显卡的新版本驱动能修改一些游戏,图形显示的BUG,所以新版本的显卡驱动能有效的利用显卡的资源,提高游戏性能。不仅可以修正旧版本中的BUG,而且可以进一步挖掘显卡硬件的功能,使得部分硬件功能得以充分发挥...
- w7旗舰版系统安装无线网卡(win7系统安装无线网卡)
-
要在Windows7中安装无线网卡,请按照以下步骤进行操作:1.检查您的计算机是否已安装无线网卡。您可以通过右键单击“我的电脑”并选择“属性”来查看计算机的硬件设置。如果计算机没有内置无线网卡,则...
- 腾达路由器管理员密码是什么
-
1、旧版本的腾达路由器,默认的用户名和密码都是:admin。?旧版腾达路由器的初始密码是:admin2、目前腾达新推出的无线路由器,在出厂状态下,是没有初始管理员密码的。?新版腾达路由器没有初始密码新...
- 电脑开机只有一个鼠标箭头黑屏
-
解决方法如下:1、同时按“ctrl+shlft+exc”键,调出任务管理器。2、点击任务管理器左下角的“详细信息”。3、然后点击左上角“文件”里的“运行新任务”。4、弹出新窗口,输入“explorer...
- 把vx好友删了想找回聊天记录
-
没有啦,联系人列表里没有了,聊天记录就没有了,无法进行恢复,收不到好友消息微信删除好友时会同时删除与该联系人的聊天记录,不过对方还是有双方的微信聊天记录的,删除好友后将无法发送消息给对方,所以伙伴们在...
- 163邮箱密码正确就是登不上(163邮箱密码一直错误)
-
邮箱不能登录或登录异常的原因有很多种哦,如您浏览器“隐私”或“安全”级别设置过高,或用户名、密码输入不正确、较长时间未登录被冻结等都会导致不能登录或登录异常。请您先检查一下哦。解决无法登录的方法有:...
- 一周热门
-
-
飞牛OS入门安装遇到问题,如何解决?
-
如何在 iPhone 和 Android 上恢复已删除的抖音消息
-
Boost高性能并发无锁队列指南:boost::lockfree::queue
-
大模型手册: 保姆级用CherryStudio知识库
-
用什么工具在Win中查看8G大的log文件?
-
如何在 Windows 10 或 11 上通过命令行安装 Node.js 和 NPM
-
威联通NAS安装阿里云盘WebDAV服务并添加到Infuse
-
Trae IDE 如何与 GitHub 无缝对接?
-
idea插件之maven search(工欲善其事,必先利其器)
-
如何修改图片拍摄日期?快速修改图片拍摄日期的6种方法
-
- 最近发表
- 标签列表
-
- python判断字典是否为空 (50)
- crontab每周一执行 (48)
- aes和des区别 (43)
- bash脚本和shell脚本的区别 (35)
- canvas库 (33)
- dataframe筛选满足条件的行 (35)
- gitlab日志 (33)
- lua xpcall (36)
- blob转json (33)
- python判断是否在列表中 (34)
- python html转pdf (36)
- 安装指定版本npm (37)
- idea搜索jar包内容 (33)
- css鼠标悬停出现隐藏的文字 (34)
- linux nacos启动命令 (33)
- gitlab 日志 (36)
- adb pull (37)
- python判断元素在不在列表里 (34)
- python 字典删除元素 (34)
- vscode切换git分支 (35)
- python bytes转16进制 (35)
- grep前后几行 (34)
- hashmap转list (35)
- c++ 字符串查找 (35)
- mysql刷新权限 (34)
