介绍
Vue 动态组件的应用场景很多,可应用于动态页签,动态路由等场景,其核心原理是批量注册。在Vue2和Vue3中实现原理相同,只是语法略有差异。
Vue2 实现
基于 webpack
require.context() 是webpack提供的一个自动导入的API
参数1:加载的文件目录
参数2:是否加载子目录
参数3:正则,匹配文件
返回值:导入函数 fn
使用require提供的函数context加载某一个目录下所有的.vue后缀的文件,他的返回值是一个对象,对象里面有一个属keys(), 可以获取所有的文件路径,我们可以遍历importFn.keys(),最后在遍历中使用
首先先描述下应用场景,比如我想在父容器遍历组件集合,根据组件类型(字符串)来首先动态加载组件,如下图:
如果要是按正常一个个注册的话,也是可以的,就是代码冗余多,不够优雅,所以需要一个方法来实现,这个方法就放到一个JS里去,然后把该index.js直接丢到widget-attr文件夹里.
//index.js
const requireComponent = require.context('./', false, /\w+\.vue$/)
let comps = {}
requireComponent.keys().map(fileName => {
let comp = requireComponent(fileName).default;
comps[comp.name] = comp
})
export default comps;
然后在页面引用,如下图:
好了,到这里简简单单就实现了,我现在整个项目需要这样批量注册的场景也就两三个,所以我在需要批量注册的组件对应的文件夹就放置这个一个index.js就能实现了,如果实际场景不想放那么多,可以自行稍微改造下,传个路径进去,我Vue3版本就是这样实现的
Vue3 实现
基于 Vite
const components = import.meta.glob("./*.vue");
//注意 : import.meta.glob 不支持变量,
Vue3 使用 组合式 ,方法里也用到了Vite的语法,首页也要把核心代码封装到 dynamicComponents.js里面
详细如下
// utils/dynamicComponents.js
import { defineAsyncComponent } from 'vue';
function loadComponentsFromFolder(folderPath) {
const components = {};
let modules = import.meta.glob('@/components/form-designer/widget/*.vue', { eager: false });
if (folderPath == 'widgetArrt') {
modules = import.meta.glob('@/components/form-designer/widget/widget-attr/*.vue', { eager: false });
}
for (const path in modules) {
const componentName = path.match(/\/([^/]+)\.vue$/)[1];
components[componentName] = defineAsyncComponent(modules[path]);
}
return components;
}
export default loadComponentsFromFolder;
这个并不完美,理想中应该是根据传参(需要动态注册的组件所在文件夹路径)来实现,但是,如下目前好像并不支持:
// utils/dynamicComponents.js
function loadComponentsFromFolder(folderPath) {
//省略...
modules = import.meta.glob(`${folderPath}/*.vue`, { eager: false });
//省略...
}
export default loadComponentsFromFolder;
这样写会报错,提示import.meta.glob不支持变量.
[plugin:vite:import-glob] Invalid glob import syntax:
Expected glob to be a string, but got dynamic template literal
//大致意思: 只能使用文本,而我们的 path 使用了变量,所以会报错.
这个有其他解决办法,待会下面会说到,因为我在该项目批量注册应用场景不多,所以我就直接传参判断来写死了.
.接下来就是引用了,如下图:
至此,就实现批量动态注册了,另外注意, 组件集合不要用绑定模式,虽然不报错,但是会报黄提示影响效率
// let components =ref({}) //不要写成响应式的了,会有性能风险提示
let components = {}
components = loadComponentsFromFolder('widget')
最后
用其他办法来解决这个问题吧,有点复杂,有更好办法的小伙伴可以留言~
使用 fs 模块读取文件列表:
在 Node.js 环境中使用 fs 模块读取指定文件夹下的文件列表。
将文件列表传递给前端,前端再使用 import() 动态导入这些文件。
前端动态导入:
前端根据接收到的文件列表动态导入组件。
实现步骤
1. 后端读取文件列表
首先,在 Vite 项目的 vite.config.js 或者单独的 Node.js 脚本中,使用 fs 模块读取文件列表,并将结果暴露给前端。
javascript
// vite.config.js 或者单独的 Node.js 脚本
const fs = require('fs');
const path = require('path');
function getComponentPaths(folderPath) {
const baseFolderPath = path.resolve(__dirname, 'src/components/form-designer/widget/');
const fullFolderPath = folderPath ? path.join(baseFolderPath, folderPath) : baseFolderPath;
const files = fs.readdirSync(fullFolderPath);
const componentPaths = files
.filter(file => file.endsWith('.vue'))
.map(file => path.join(fullFolderPath, file));
return componentPaths.map(p => p.replace(/\\/g, '/').replace(path.resolve(__dirname, 'src/'), '@/'));
}
module.exports = {
getComponentPaths,
};
2. 前端动态导入
在前端,使用 import() 动态导入这些文件。
javascript
// utils/dynamicComponents.js
import { defineAsyncComponent } from 'vue';
const componentCache = {};
async function loadComponentsFromFolder(folderPath) {
// 检查缓存
if (componentCache[folderPath]) {
return componentCache[folderPath];
}
// 获取文件路径列表
const componentPaths = await fetchComponentPaths(folderPath);
const components = {};
// 动态导入模块
await Promise.all(componentPaths.map(async (path) => {
const componentName = path.match(/\/([^/]+)\.vue$/)[1];
components[componentName] = defineAsyncComponent(() => import(path));
}));
// 缓存结果
componentCache[folderPath] = components;
return components;
}
async function fetchComponentPaths(folderPath) {
// 这里假设你有一个 API 端点来获取文件路径列表
const response = await fetch(`/api/get-component-paths?folderPath=${folderPath}`);
const data = await response.json();
return data.paths;
}
export default loadComponentsFromFolder;
3. 创建 API 端点
在 Vite 项目中创建一个简单的 API 端点来返回文件路径列表。
javascript
// server/index.js
const express = require('express');
const { getComponentPaths } = require('../vite.config');
const app = express();
const port = 3000;
app.get('/api/get-component-paths', (req, res) => {
const folderPath = req.query.folderPath;
const paths = getComponentPaths(folderPath);
res.json({ paths });
});
app.listen(port, () => {
console.log(`Server running at http://localhost:${port}`);
});
使用示例
在组件或页面中使用 loadComponentsFromFolder 函数时,只需传递不同的 folderPath 参数即可动态注册不同路径下的组件。
javascript
// 在某个 Vue 组件中使用
<script setup>
import { ref, onMounted } from 'vue';
import loadComponentsFromFolder from '@/utils/dynamicComponents';
const folderPath = 'widgetAttr'; // 可以根据实际需求动态设置
const dynamicComponents = ref({});
onMounted(async () => {
dynamicComponents.value = await loadComponentsFromFolder(folderPath);
});
</script>
<template>
<div>
<component v-for="(component, name) in dynamicComponents" :is="component" :key="name"></component>
</div>
</template>
注意事项
文件路径处理:
确保文件路径在前后端一致,特别是在 Windows 系统中,路径分隔符需要转换。
API 端点:
确保 API 端点能够正确返回文件路径列表。
性能优化:
如果组件数量较多,可以考虑使用懒加载和缓存机制来优化性能。
通过这种方式,你可以根据路径参数动态注册不同文件夹下的组件,而不需要使用 if-else 判断。