给我5分钟,保证教会你在vue3中动态加载远程组件

news/2024/11/20 12:26:29/文章来源:https://www.cnblogs.com/heavenYJJ/p/18346051

前言

在一些特殊的场景中(比如低代码、减少小程序包体积、类似于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 === ".")// selfreturn 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。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/779101.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

结合实例看 maven 传递依赖与优先级,难顶也得上丫

开心一刻 想买摩托车了,但是钱不够,想找老爸借点 我:老爸,我想买一辆摩托车,上下班也方便 老爸:你表哥上个月骑摩托车摔走了,你不知道?还要买摩托车? 我:对不起,我不买了 老板:就是啊,骑你表哥那辆得了呗,买啥新的先抛问题 关于 maven 的依赖(dependency),我相…

EmpireCMS_V7.5 sql注入漏洞

没有什么好说的侵权声明 本文章中的所有内容(包括但不限于文字、图像和其他媒体)仅供教育和参考目的。如果在本文章中使用了任何受版权保护的材料,我们满怀敬意地承认该内容的版权归原作者所有。 如果您是版权持有人,并且认为您的作品被侵犯,请通过以下方式与我们联系: […

增强用户体验:2个功能强大的.NET控制台应用帮助库

前言 对于.NET开发者而言,构建控制台应用程序时,如何提升用户交互的流畅性和满意度,是一个持续探索与优化的话题。今天大姚给大家分享2个功能强大的.NET控制台应用帮助库,希望可以帮助大家能够快速的构建漂亮、强交互性、丰富功能的控制台应用程序。 Terminal.Gui Terminal…

2024牛客暑期多校训练营7

你说的对但是今天是伟大的御阿礼之子第九代稗田阿求同志的三十华诞目录写在前面JIKDCH写在最后 写在前面 比赛地址:https://ac.nowcoder.com/acm/contest/81602#question 以下按个人难度向排序。 dztlb 大神回去补办身份证了,于是单刷,打的像史。 呃呃抽象场。 J 签到。 手玩…

读零信任网络:在不可信网络中构建安全系统11用户组的认证和授权

读零信任网络:在不可信网络中构建安全系统11用户组的认证和授权1. 用户组的认证和授权 1.1. 几乎在每个系统中都有一小部分操作需要被密切关注1.1.1. 每个应用对这部分操作的风险容忍度各有不同,且没有任何下限1.1.2. 一部分风险是由用户个人的可信度决定的1.1.2.1. 单个用户…

软件测试设计1探索性测试

1 探索性测试 本章将介绍探索性测试:手动试用新功能,快速获得有关其行为的反馈。我们将详细介绍探索性测试,考虑它的优缺点,以及何时应在项目中执行探索性测试。 我们将了解开始探索性测试所需的先决条件以及应采取的方法。这种测试可以是完整测试计划的一个缩影,它从客户…

Kubernetes高级部署组件 Argo Rollout

Argo介绍 https://argoproj.github.io/ Argo 是一个开源项目,旨在提供一套用于在 Kubernetes 上运行和管理容器化工作负载的工具。 Argo 项目最早在2017年由Applatix公司创立开源,在2018年被美国加利福尼的 Intuit 公司收购并持续维护,并得到了广泛的社区支持。 Argo 项目主要…

n00bzCTF 2024

n00bzCTF 2024n00bzCTF 2024 Crypto Vinegar 题目:Encrypted flag: nmivrxbiaatjvvbcjsf Key: secretkeyexp: 维吉尼亚密码 ​​ flag:n00bz{vigenerecipherisfun} RSA 题目:e = 3 n = 1351123252887151367278321777355120706250832196704807178418175833438514454543565797…

两个coca略有不同词频文件 比较

coca20000xlsxFuzhi.a应该也是这样弄的.txt COCA60000.txt确实有一些词顺序不同,不知道为什么。。。

【第2期】2024 搜索客 Meetup | Elasticsearch 的代码结构和写入查询流程的解读

本次活动由 搜索客社区、极限科技(INFINI Labs)联合举办,活动主题将深入探讨 Elasticsearch 的两个核心方面:代码结构以及写入和查询的关键流程。本次活动将为 Elasticsearch 初学者和有经验的用户提供宝贵的见解,欢迎大家报名参加、交流学习。 活动主题:Elasticsearch 的…

【第2期】2024 搜索客 Meetup | Elasticsearch 的代码结构和写入查询流程的解读.md

本次活动由 搜索客社区、极限科技(INFINI Labs)联合举办,活动主题将深入探讨 Elasticsearch 的两个核心方面:代码结构以及写入和查询的关键流程。本次活动将为 Elasticsearch 初学者和有经验的用户提供宝贵的见解,欢迎大家报名参加、交流学习。 活动主题:Elasticsearch 的…

使用黑群晖webdav服务同步obsidian笔记

前言 本文使用的黑群晖套件 webdav server进行黑群晖配置,接着使用obsidian插件将笔记同步到黑群晖上 使用的黑群晖已经经过内网穿透。 配置黑群晖 配置 webdav 并创建共享文件夹 首先下载套件,在套件中心搜索webdav server ,下载安装并打开进行配置此处已经安装好了,打开,…