vue 2.x 如何“主动”的动态注入组件?
vue 2.6
目前主要用的组件“异步注入”方式,大概如下:
需要注入的组件文件:
global-custom-components.js
const install = Vue => { Vue.component('comp-name', () => import(./components/compName.vue))
}
export default install
然后main.js
import customComponents from './global-custom-components.js'Vue.use(customComponents)
然后当某个包含组件 comp-name
的页面被打开时,该组件才会被下载、导入,从而达到“减负”的做用。
<comp-name></comp-name>
但是感觉这种方式还是显得比较“隐式”或者说“被动”,当某个页面使用到该组件时,该组件非常隐蔽的被“下载”下来,这是不受我们控制的。
现在的有一个需求是,我需要在组件开始下载的过程,显示一个动画,当组件被完全下载,动画结束,然后展示这些组件。
假设,有一个预览页面,页面上有 10 个自定义组件,我需要做如下步骤:
let compList = [comp_1, comp2, ......] // 共 10 个组件
1、展示 loading 动画效果;
2、循环判断 compList 当前vue实例是否包含了每一个组件(因为组件类型相同,虽然有 10 个组件,但实际上只有 5 类不统的组件);
3、如果不存在,同步导入组件;
4、当组件全部导入完毕;
5、暂停动画并展示组件;
请问第三步该如何实现?
——————————— 分割线 ——————————
@lining 感谢回复,我回答的内容太长,保存不了,以补充形式回复在这,感谢!
看了回复,但还是不太明白,感觉没回到到点上。
我尝试着写了一个 mixins
,里面有一个函数用来动态注册组件,发现有如下问题,伪代码如下:
loadComp() { //组件json对象,包含组件需要的一些参数
let componentList = []
componentList.forEach(async (e) => {
let targetData = e
// 实际监听,能获取到正确的组件路径
let path = `../../../packages/components/${targetData.package}/${targetData.category}/${targetData.key}/index.vue`
let key = `V${targetData.key}`
let ifExists = Vue.component(key)
if(!ifExists) {
Vue.component(
key,
() => import(path)
)
}
})
},
上面代码有如下问题:
1、Vue.component(key),key为任何参数,该方法在 bool 环境下都为 true,所以不能用来判断 xxx 组件是否被注册过;
2、如下代码,在 path 获取到正确的路径后,
Vue.component( key,
() => import(path)
)
执行报错,错误信息
Failed to resolve async component: () => __webpack_require__("./src/views/preview/mixins lazy recursive")(path)Reason: Error: Cannot find module '../../../packages/components/Charts/Bars/BarCrossrange/index.vue'
—————————————— 分割线 ————————————
—————————————— 答案 ————————————
定义获取组件js文件:
// 通过 require.context 找到路径“./components/”下的所有 index.vue 文件
// 获取到政府列表,如:
// ./components/comp_a/index.vue
// ./components/comp_b/index.vue
// ./components/comp_c/index.vue
// 所有组件均已 export default {} 方式定义——vue 2.6(以下) 的通用方式
const modules = require.context(
// 其组件目录的相对路径
'./components/',
// 是否查询其子目录
true,
// 匹配基础组件文件名的正则表达式
// /Base[A-Z]\w+\.(vue|js)$/
/\w+\/index.vue/
)
// 通过组件名称 compName(comp_a),获取组件
export const fetchComponent = (compName) => {
const module = modules
let keys = module.keys()
for(let i = 0; i < keys.length; i++) {
let key = keys[i]
const urlSplit = key.split('/')
if(urlSplit[urlSplit.length -2 ] === compName) {
const componentConfig = module(key).default
// 返回组件对象
return componentConfig.default || componentConfig
}
}
}
安装组件文件:
import Vue from 'vue'/**
* * 动态注册组件
*/
export const componentInstall = (key, node) => {
//如果key组件没有被注册过,且组件对象node存在,则注册该组件
if(!Vue.component(key) && node) {
console.log('注册组件:' + key)
Vue.component(key, node)
}else{
console.log('组件:' + key + ' 已存在')
}
}
消费文件:
componentInstall('comp_a', fetchComponent('comp_a'))
—————————————— 分割线 ————————————
—————————————— 答案 (最终)————————————
在之前得到的答案中,其实还是有问题的,我发现所有组件都被打包到app.js
里面了,这样就失去了异步加载的意义了。
经过我的测试,发现问题出在require.context()
方法,该方法把匹配到的文件全部打包进app.js
了,所以不能用此方法。
const modules = require.context( // 其组件目录的相对路径
'./components/',
// 是否查询其子目录
true,
// 匹配基础组件文件名的正则表达式
// /Base[A-Z]\w+\.(vue|js)$/
/\w+\/index.vue/
)
经过我的测试,发现问题其实很简单,如果你只需要单个组件异步加载(不需要多个组件“全部”完成加载的场景),那么直接可以:
//item.chartKey 组件名称//() => fetchChartComponent(item) 异步加载组件对象
//注册异步组件 item.chartKey,页面可以使用该组件<item.chartKey></item.chartKey>,组件下载完成后,自动展示在页面上。
//重要(注意):
//也就是说,页面使用该组件时,即使组件没有下载完成,也是没有问题的。
componentInstall(item.chartKey, () => fetchChartComponent(item))
//注册异步组件 item.conKey
componentInstall(item.conKey, () => fetchConfigComponent(item))
此种方式其实和原问题描述中的使用方式一致。
export const fetchChartComponent = (chartConfig) => { const chart = import(`./components/${chartConfig.package}/${chartConfig.category}/${chartConfig.key}/index.vue`)
return chart
}
import Vue from 'vue'/**
* * 动态注册组件
*/
export const componentInstall = (key, node) => {
if(!Vue.component(key) && node) {
console.log('注册组件:' + key)
Vue.component(key, node)
}else{
console.log('组件:' + key + ' 已存在')
}
}
如果你需要等很多组件全部加载完成后,再执行某个逻辑,那么可以写
let f1 = fetchChartComponent(item1)let f2 = fetchChartComponent(item2)
let f3 = fetchChartComponent(item3)
//重要(注意):
//此时,在所有组件未"全部"加载完成前,页面不能使用到了某个为下载完成的组件。
//通常,此种场景下,会在页面所有组件最外层套一个div,然后用 if 来控制,如果全部加载完成,该div才能展示(v-if='true')。
this.loading = true
Promise.all([f1, f2, f3]).then(res => {
this.loading = false
}).catch(err => {
this.loading = false
})
回答:
Vue.component('comp-name', () => import(./components/compName.vue))
这句话拆开看看
() => import(./components/compName.vue)
继续拆开
function(){ return import(./components/compName.vue)
}
现在明白了吗?这本质是一个 function
https://v2.cn.vuejs.org/v2/guide/components-dynamic-async.html#%E5%BC%82%E6%AD%A5%E7%BB%84%E4%BB%B6
回答:
可以看一下这个项目 unplugin-vue-components,应该是符合你预期的。
Anthony Fu 这位佬还提供、参与了了其它类似项目的开发,都可以了解一下。
以上是 vue 2.x 如何“主动”的动态注入组件? 的全部内容, 来源链接: utcz.com/p/933201.html