给我 5 分钟,保证教会你在 vue3 中动态加载远程组件
前言
在一些特殊的场景中(比如低代码、减少小程序包体积、类似于 APP 的热更新),我们需要从服务端动态加载.vue
文件,然后将动态加载的远程 vue 组件渲染到我们的项目中。今天这篇文章我将带你学会,在 vue3 中如何去动态加载远程组件。
defineAsyncComponent
异步组件
想必聪明的你第一时间就想到了defineAsyncComponent
方法。我们先来看看官方对defineAsyncComponent
方法的解释:
定义一个异步组件,它在运行时是懒加载的。参数可以是一个异步加载函数,或是对加载行为进行更具体定制的一个选项对象。
defineAsyncComponent
方法的返回值是一个异步组件,我们可以像普通组件一样直接在 template 中使用。和普通组件的区别是,只有当渲染到异步组件时才会调用加载内部实际组件的函数。
我们先来简单看看使用defineAsyncComponent
方法的例子,代码如下:
defineAsyncComponent
方法接收一个返回 Promise 的回调函数,在 Promise 中我们可以从服务端获取 vue 组件的 code 代码字符串。然后使用resolve(/* 获取到的组件 */)
将拿到的组件传给defineAsyncComponent
方法内部处理,最后和普通组件一样在 template 中使用AsyncComp
组件。
从服务端获取远程组件
有了defineAsyncComponent
方法后事情从表面上看着就很简单了,我们只需要写个方法从服务端拿到 vue 文件的 code 代码字符串,然后在defineAsyncComponent
方法中使用resolve
拿到的 vue 组件。
第一步就是本地起一个服务器,使用服务器返回我们的 vue 组件。这里我使用的是http-server
,安装也很简单:
使用上面的命令就可以全局安装一个 http 服务器了。
接着我在项目的 public 目录下新建一个名为remote-component.vue
的文件,这个 vue 文件就是我们想从服务端加载的远程组件。remote-component.vue
文件中的代码如下:
从上面的代码可以看到远程 vue 组件和我们平时写的 vue 代码没什么区别,有template
、ref
响应式变量、style
样式。
接着就是在终端执行http-server ./public --cors
命令启动一个本地服务器,服务器默认端口为8080
。但是由于我们本地起的 vite 项目默认端口为5173
,所以为了避免跨域这里需要加--cors
。./public
的意思是指定当前目录的public
文件夹。
启动了一个本地服务器后,我们就可以使用 http://localhost:8080/remote-component.vue链接从服务端访问远程组件啦,如下图:
从上图中可以看到在浏览器中访问这个链接时触发了下载远程 vue 组件的操作。
defineAsyncComponent
加载远程组件
接下来我们就是在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
代码:
父组件代码:
上面的 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
包后的代码,如下:
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 字符串插入到页面中。
完整父组件代码如下:
在上面的完整例子中,首先渲染了本地组件LocalChild
。然后当点击“加载远程组件”按钮后再去渲染远程组件RemoteChild
。我们来看看执行效果,如下图:
从上面的 gif 图中可以看到,当我们点击“加载远程组件”按钮后,在 network 中才去加载了远程组件remote-component.vue
。并且将远程组件渲染到了页面上后,通过按钮的点击事件可以看到远程组件的响应式依然有效。
vue3-sfc-loader
同时也支持在远程组件中去引用子组件,你只需在options
额外配置一个pathResolve
就行啦。pathResolve
方法配置如下:
其实vue3-sfc-loader
包的核心代码就 300 行左右,主要就是调用 vue 暴露出来的一些底层 API。如下图:
总结
这篇文章讲了在 vue3 中如何从服务端加载远程组件,首先我们需要使用defineAsyncComponent
方法定义一个异步组件,这个异步组件是可以直接在 template 中像普通组件一样使用。
但是由于defineAsyncComponent
接收的组件必须是编译后的 vue 组件对象,而我们从服务端拿到的远程组件就是一个普通的 vue 文件,所以这时我们引入了vue3-sfc-loader
包。vue3-sfc-loader
包的作用就是在运行时将一个 vue 文件编译成 vue 组件对象,这样我们就可以实现从服务端加载远程组件了。
文章转载自:前端欧阳
评论