vue3+vite项目,组件动态导入

2,742 阅读4分钟

场景

很多时候,我们可能会遇到这样的场景:有一个动态菜单、标签、按钮...等,用户点击不同的菜单、按钮、标签,对应的页面(动态组件渲染部分)会渲染对应的组件内容,我写了给演示样例,如图:

image.png

image.png

动图演示:

动画.gif

功能实现

对于上述需求,有的人可能是这样实现的:

注:此处是演示代码,实际环境的配置数据可能是通过后端接口获取的,比如权限系统的菜单配置,前端的路由是通过接口获取数据,并动态呈现的,大家看个实现思路就好。

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、页面效果就出来了

image.png

问题分析

对于上面这种组件导入方式:随着组件的增加,每次都需要手动复制一行导入代码和注册组件名称,能不能有一种方式能让他自动导入?不再用手动去添加?

方案1

利用声明路由的方式去声明当前选中的组件

1、首先,观察一下我们的路由声明方式

image.png

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`);
        }

保存,然后报错了:

image.png

报错的意思是:

这个错误提示来自 Vite 的 import 分析插件
这个错误是因为 Vite 无法分析出上面动态 import 的类型,因为它是以变量的形式,而不是字面量形式写的

解决方案:

1、在 import 语句后面添加:/* @vite-ignore */ 忽略这个 warning(控制台的报错信息里面已经写出来了)
2、第二种方式,如下:

        // 切换渲染组件
        changeRenderComponent(component: string) {
            this.renderComponent = () => import(`../../component/autoImports/${component}.vue`);
        }

保存,不报错了,然后进入页面,点击按钮,然后又报错了

image.png

报错的意思大概是:这个错误通常是由于在尝试访问一个空的子树(subTree)引|起的。在 Vue3 中,SubTree 是一个在渲染组件时创建的内部数据结构,它可以被用来跟踪父子组件之间的关系等信息。

注意这句话:SubTree 是一个在渲染组件时创建的内部数据结构

思考:报错说我们声明的选中组件变量中的SubTree为空,而SubTree是渲染组件时创建的内部数据结构,那么它就是在告诉我们,你给它的东西他不认识,因为不完整,所以不能完成。

这个时候,我们的那种方式就行不通了,我们看Vue官方文档给出的一种方式:

image.png

我们看到这里:

image.png

官方文档给出的方式,是在我们刚才的函数外面,套用它的函数,这说明我们的方向没有错,只是赋值方式不对;让我们更改一下代码:

        // 切换渲染组件
        changeRenderComponent(component: string) {
            this.renderComponent = defineAsyncComponent(() => import(`../../component/autoImports/${component}.vue`));
        }

那么这样呢,我们的功能就实现了:

动画.gif

ps:当你点击按钮时,控制台会有一个警告:

image.png

翻译过来,意思是: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)

image.png

image.png

这两种方式在vue3中都是可以的,主要是我之前使用vue2比较多,所以我个人习惯用第一种方式(图1),至于这两种方式的含义介绍那就自行查阅去吧,往深处讲,这一篇文章也讲不完;无论是使用哪种方式都取决于你的个人习惯、公司规定、项目环境等

image.png

方案2

那么除了使用vue框架提供是方式,我们是否可以使用其他方式呢?
我们看vite官方文档(我这里使用的构建工具是:vite):

image.png

当然,如果你使用的是webpack,那么也有相应的方案:

image.png

翻译后的中文文档:

image.png

1、使用vite的动态导入方案

image.png

// 导入组件
const modules = import.meta.glob('@/component/autoImports/**.vue');

console.log(modules);

我们来看一下modules的内容:

image.png

2、将导入的modules,运用到切换组件函数中

// 切换渲染组件
const changeRenderComponent = function (component: string) {
    renderComponent.value = defineAsyncComponent(modules[`/src/component/autoImports/${component}.vue`]);
};

写完后呢,效果也是一样的:

动画.gif

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>

这样效果不就有了

动画1.gif

那么本期文章到此就结束了~

结语

如果你有更好的方法或方案可以在下方留言讨论哦~
如有不懂或者疑问,可在下方留言或私信我,看到会回
希望对你能有所帮助,如果觉得文章写的不错,欢迎点赞/收藏,三克油~