vue组件如何渲染到页面(vue3 使用defineAsyncComponent与component标签实现动态渲染组件)
内容有些啰嗦 ,内容记载了当时遇到了bug以及解决问题的思路 。
业务场景简述: 前端做配置化组件 ,通过url内的唯一标识 ,请求后端sql 哪取页面配置信息 ,前端通过配置信息动态渲染查询表单 ,导出 、表格 ,toolbar以及动态弹窗;该动态渲染组件的功能 ,就是渲染的toolbar内的按钮 ,类型为自定义弹窗 ,弹窗路径为后端配置数据 ,前端通过点击该按钮 ,打开指定路径下的弹窗组件 。
之前用的vue2的动态挂载组件 ,也没见vue3那么麻烦,官网上的例子以及网上的所以例子都是前端知道要导哪些组件 ,然后程序进入就直接导进来了 ,只是写了逻辑动态切换 。我这个不一样,需要挂载的组件是未知的 。所以实现起来有点吃力 。
一 、基础的动态引入组件: 简单的动态引入的意思是 ,前端知道要引入哪些组件 ,将多个组件引入到父组件中 ,但不渲染它 ,满足一定条件后 ,才去在某个位置渲染指定的组件 。
<template> <custom-modal ref="custom"></custom-modal> </template> <script> import { reactive, ref, shallowReactive, onActivated, defineAsyncComponent, } from vue; const customModal = defineAsyncComponent(() => import(./modal/CustomModal.vue)); const custom = ref(); </script>以上的例子就是通过vue的defineAsyncComponent实现挂载组件 ,并赋值给customModal ,模板中可以直接使用<custom-modal>作为标签使用,也可以将它赋值给component中的is属性 ,is属性执向一个变量 ,可通过业务逻辑动态 ,更改该变量的值 ,就可以实现多个组件进行来回的渲染了
<template> <component :is="componentKey" ref="custom"></component> </template> import { reactive, ref, shallowReactive, onActivated, defineAsyncComponent, } from vue; const componentKey = ref(null); const components: any = shallowReactive({}); const customModal = defineAsyncComponent(() => import(./modal/CustomModal.vue)); componentKey = customModal二 、复杂的引入:不确定到底引入什么组件 ,组件的路径由后端返回
将以上代码 添加到项目代码中,并不能实现 ,虽然引入不报错 ,但是ref一直为undefined,无法调用动态组件内的open函数 。
不断尝试了很多次 ,得出以下结论 1.起初是在按钮的click函数内去挂载自定义组件并调用ref函数的 ,ref为undefined 。 尝试多次不能实现功能(这里是挂载与调用最合适的位置) , 2.接着又在初始化配置数据时(查询后端sql) ,axios的then函数内挂载组件 ,然后点击按钮的地方调用ref内的函数 ,ref依旧为null 。 3. 接着在最外层 ,调用初始化时挂载 ,也就是生命周期函数体内 ,测试还是一样的结果 。 4. 接着发现带有async函数体内挂载组件 ,也无法完成 。 5.单独写个函数 ,不加async ,函数内挂载组件,然后再生命周期外调用该函数 ,按钮内调用ref内的方法 ,成功弹窗 。这并不是我想要的,因为路径不是固定的 ,它要等到后端sql放回结果 ,才能执行。总结:上面的多次测试 ,得出以下结论 ,都不能让动态组件ref对象有值
1 、不能在组件的事件函数内挂载 ,
2 、不能在axios的then函数体内挂载
3 、不能在带有async声明的函数体内挂载
4 、不能在vue的生命周期内挂载
5 、只能在最外层挂载实现 ,这时ref才是个对象 。
好在天无绝人之路;脑海里有个思路:
页面初始化时将项目里所有的全局挂载view组件扔到一个object内 ,使用component组件 ,is:对应object内指定的组件对象 ,然后通过后端的数据 ,这时后端就不用给组件路径了 ,给个组件名 ,我从object中找到挂载的组件然后将对象给is 。
const modules = import.meta.glob(@/views/*/**.vue); // 获取所有项目路径
mudules为views内所有的vue的相对路径,然后循环它 ,在循环体内实现挂载 ,将它存入一个对象内,key为相对路径的项目名称(可以截取以下)。有了上面的思路 ,通过反复测试和实现 ,最终功能实现了 。
<template> <component :is="componentKey" ref="custom"></component> </template> <script> import { reactive, ref, shallowReactive, onActivated, defineAsyncComponent, } from vue; //声明componentkey,用于告诉component当前挂载什么组件 ,components为一个对象 ,存放多个不确定的自定义组件 。 const componentKey = ref(null); const components: any = shallowReactive({}); // 组件挂载 const initTableConfig = (gridId, type) => { queryTableConfig({ gridId }).then(({ data }) => { if (type === main) { Object.assign(mainConfig, data); tabsKey.value = -1; } else { tabsDetail.value.push(data); tabsKey.value = tabsDetail.value.length - 1; } // 涉及到自定义组件的部分 ,这里需要提前挂载 ,在用到时不至于ref为null XEUtils.objectEach(data.action, (action, key) => { if ( action.modalCfg && action.modalCfg.type === CustomModal && action.modalCfg.src ) { components[action.actionId] = defineAsyncComponent( () => import(`../../../${action.modalCfg.src}`) ); //注意:这里的路径后端只能返回相对路径 ,不能使用@/xxx/xxx.vue ,不能使用src/xxx/xxx.vue ,只能./xxx.vue或者../../xxx/xxx.vue 。由于并不确定组件在什么位置 ,避免容易出错的原则 ,我在前端通过../../../的形式将路径回退到src下 ,后端只需要从src下配置路径即可 ,不用考虑那么多了 。如后端src的值为src/xxx/xxx/xxx.vue 则在前端合成的路径就为../../../src/xx/xxx/xxx.vue componentKey.value = components[action.actionId]; // 为什么componentKey.vue在这里赋值,在后面点击窗口后又赋值 ,这里能不能省略 。 // 答:这里省略的话 ,到点击按钮触发时会报错,第一次点击会报错 ,第二次点击不会报错 ,窗口正常弹出 。可能是因为 ,组件挂载时并没有引入组件 ,只在使用时才引入 ,如果上面不提前将挂载好的组件引入进来 ,后面触发事件触发时引入在调用ref ,执行太快 ,costom就会报错 ,所以才会点两次才弹窗 。 } }); }); }; </script>按钮点击触发事件 ,确定弹窗要弹出什么组件
} else if (action.modalCfg.type === CustomModal) { // 这里的actionid和组件是对应的 ,所以在按钮触发后 ,通过按钮携带的actionid能取到对应的组件 。 componentKey.value = components[action.actionId]; custom.value.init(row); }经过以上的方式:在任何地方挂载都不会报错,完美解决 。
注意:挂载与使用ref不能在同一个方法体内 ,如果可以的话 ,页面加载时,执行挂载 ,需要调用ref时就不会报错 。创心域SEO版权声明:以上内容作者已申请原创保护,未经允许不得转载,侵权必究!授权事宜、对本内容有异议或投诉,敬请联系网站管理员,我们将尽快回复您,谢谢合作!