场景
很多时候,我们可能会遇到这样的场景:有一个动态菜单、标签、按钮...等,用户点击不同的菜单、按钮、标签,对应的页面(动态组件渲染部分)会渲染对应的组件内容,我写了给演示样例,如图:
动图演示:
功能实现
对于上述需求,有的人可能是这样实现的:
注:此处是演示代码,实际环境的配置数据可能是通过后端接口获取的,比如权限系统的菜单配置,前端的路由是通过接口获取数据,并动态呈现的,大家看个实现思路就好。
1、声明所需的变量
// 按钮组配置数据
btnCompoents: [
{
name: 'Com01组件',
component: 'Com01'
},
{
name: 'Com02组件',
component: 'Com02'
},
{
name: 'Com03组件',
component: 'Com03'
},
{
name: 'Com04组件',
component: 'Com04'
}
],
// 要渲染的组件
renderComponent: 'Com01'
2、渲染页面
<!-- 切换按钮组 -->
<div class="btn_group">
<div v-for="item in btnCompoents" :key="item.name">
<el-button @click="renderComponent = item.component">{{ item.name }}</el-button>
</div>
</div>
<!-- 动态组件渲染 -->
<div class="render_component">
<component :is="renderComponent"></component>
</div>
3、记得导入所需组件,并注册
// 导入组件
import Com01 from '@/component/autoImports/Com01.vue';
import Com02 from '@/component/autoImports/Com02.vue';
import Com03 from '@/component/autoImports/Com03.vue';
import Com04 from '@/component/autoImports/Com04.vue';
// ....其他代码省略
components: {
// 注册组件
Com01,
Com02,
Com03,
Com04
}
// ....
4、页面效果就出来了
问题分析
对于上面这种组件导入方式:随着组件的增加,每次都需要手动复制一行导入代码和注册组件名称,能不能有一种方式能让他自动导入?不再用手动去添加?
方案1
利用声明路由的方式去声明当前选中的组件
1、首先,观察一下我们的路由声明方式
2、我们试一下用这种方式来自动导入组件
<!-- 切换按钮组 -->
<div class="btn_group">
<div v-for="item in btnCompoents" :key="item.name">
<!-- 将此处换成函数来处理点击事件 -->
<el-button @click="changeRenderComponent(item.component)">{{ item.name }}</el-button>
</div>
</div>
ps:函数呢也很简单,无非就是把变量赋值改成路由的方式
// 切换渲染组件
changeRenderComponent(component: string) {
this.renderComponent = () => import(`@/component/autoImports/${component}.vue`);
}
保存,然后报错了:
报错的意思是:
这个错误提示来自 Vite 的 import 分析插件
这个错误是因为 Vite 无法分析出上面动态 import 的类型,因为它是以变量的形式,而不是字面量形式写的
解决方案:
1、在 import 语句后面添加:
/* @vite-ignore */
忽略这个 warning(控制台的报错信息里面已经写出来了)
2、第二种方式,如下:
// 切换渲染组件
changeRenderComponent(component: string) {
this.renderComponent = () => import(`../../component/autoImports/${component}.vue`);
}
保存,不报错了,然后进入页面,点击按钮,然后又报错了
报错的意思大概是:这个错误通常是由于在尝试访问一个空的子树(subTree)引|起的。在 Vue3 中,SubTree 是一个在渲染组件时创建的内部数据结构,它可以被用来跟踪父子组件之间的关系等信息。
注意这句话:SubTree 是一个在渲染组件时创建的内部数据结构
思考:报错说我们声明的选中组件变量中的SubTree为空,而SubTree是渲染组件时创建的内部数据结构,那么它就是在告诉我们,你给它的东西他不认识,因为不完整,所以不能完成。
这个时候,我们的那种方式就行不通了,我们看Vue官方文档给出的一种方式:
我们看到这里:
官方文档给出的方式,是在我们刚才的函数外面,套用它的函数,这说明我们的方向没有错,只是赋值方式不对;让我们更改一下代码:
// 切换渲染组件
changeRenderComponent(component: string) {
this.renderComponent = defineAsyncComponent(() => import(`../../component/autoImports/${component}.vue`));
}
那么这样呢,我们的功能就实现了:
ps:当你点击按钮时,控制台会有一个警告:
翻译过来,意思是:Vue收到了一个组件,该组件被制成了一个反应对象。这可能会导致不必要的性能开销,应该通过用“markRaw”标记组件或使用“shallowRef”而不是“ref”来避免。
补充知识:
shallowRef
:ref的浅层作用形式。shallowRef与普通的 ref 的区别在于,shallowRef 不会对对象进行深度的响应式处理,也就是 shallowRef 包含的对象内部的属性发生变化时,shallowRef 本身不会触发重新渲染或响应式更新,所以使用shallowRef时只关心顶层的引用变化。
markRaw
:作用是标记一个对象,使其不再被reactive
或shallowReactive
转换为响应式代理。即你之后试图用这些函数包装这个对象,它也会保持原样,不会变成响应式的。
这里的场景适合使用shallowRef来声明变量,添加下面代码后,控制台就没有警告了
// 要渲染的组件
renderComponent: shallowRef(null as any)
ps:有的人可能会问,我这里声明变量怎么是这么写的?我平时用vue3都是用的const xxx = ref()...等。
小提示:我这里是使用defineComponent来创建组件的(图1),这个主要看个人习惯,大众化的做法是使用setup(图2)
这两种方式在vue3中都是可以的,主要是我之前使用vue2比较多,所以我个人习惯用第一种方式(图1),至于这两种方式的含义介绍那就自行查阅去吧,往深处讲,这一篇文章也讲不完;无论是使用哪种方式都取决于你的个人习惯、公司规定、项目环境等
方案2
那么除了使用vue框架提供是方式,我们是否可以使用其他方式呢?
我们看vite官方文档(我这里使用的构建工具是:vite):
当然,如果你使用的是webpack,那么也有相应的方案:
翻译后的中文文档:
1、使用vite的动态导入方案
// 导入组件
const modules = import.meta.glob('@/component/autoImports/**.vue');
console.log(modules);
我们来看一下modules的内容:
2、将导入的modules,运用到切换组件函数中
// 切换渲染组件
const changeRenderComponent = function (component: string) {
renderComponent.value = defineAsyncComponent(modules[`/src/component/autoImports/${component}.vue`]);
};
写完后呢,效果也是一样的:
ps:如果你想在页面加载时就有默认的选项,只需要在页面在加载时对renderComponent赋值,或声明renderComponent时就设置默认值即可,使用哪种方式看具体的使用场景
按钮选中时的状态
这是一个小效果,就是根据用户当前选中的组件按钮,给他一个选中状态的效果,我这里使用的是EelementPlus,其他组件也可以使用类似方式实现
// 当前选中的组件名称
const chooseComname = ref('');
// 切换渲染组件
const changeRenderComponent = function (component: string) {
renderComponent.value = defineAsyncComponent(modules[`/src/component/autoImports/${component}.vue`]);
// 这里赋值一下
chooseComname.value = component;
};
再对type做个判断就行了,无论是type还是loading,或是其他组件的状态属性判断,都要根据实际需求和使用场景来变化,这里只做演示
<!-- 切换按钮组 -->
<div class="btn_group">
<div v-for="item in btnCompoents" :key="item.name">
<el-button @click="changeRenderComponent(item.component)" :type="chooseComname === item.component ? 'primary' : ''">{{
item.name
}}</el-button>
</div>
</div>
这样效果不就有了
那么本期文章到此就结束了~
结语
如果你有更好的方法或方案可以在下方留言讨论哦~
如有不懂或者疑问,可在下方留言或私信我,看到会回
希望对你能有所帮助,如果觉得文章写的不错,欢迎点赞/收藏,三克油~