vue3——defineAsyncComponent

1,247 阅读3分钟

2021/9/19

长沙,周四,晴

修改别人的代码bug很痛苦,目前我就是这样,看上一个前端小子的uniapp,优化和解决一些问题。自己负责的项目使用vue3重构工作也只能搁浅一段时间,记录一下使用vue3。

首先是代码版本:package.json文件

"version": "0.0.0",
"scripts": {
    "dev": "vite",
    "build": "vue-tsc --noEmit && vite build",
    "serve": "vite preview"
},
"dependencies": {
    "axios": "^0.21.1",
    "core-js": "^3.15.2",
    "echarts": "^5.1.2",
    "element-plus": "^1.0.2-beta.70",
    "vue": "^3.2.4",
    "vuex": "^4.0.1",
    "vuex-persistedstate": "^4.0.0-beta.3"
  },
  "devDependencies": {
    "@types/node": "^15.14.1",
    "@vitejs/plugin-vue": "^1.2.4",
    "@vue/compiler-sfc": "^3.1.4",
    "typescript": "^4.3.5",
    "vite": "^2.4.0",
    "vue-tsc": "0.0.24"
  }
  

vite.config.ts配置就不贴代码了,都是一些常规,项目是一个类似数据管理系统,页面特别多,使用标签页遍历出来页面视图,每一个视图都是一个vue文件,使用v2的时候使用component标签,在is上绑定resolve => require(['./components' + item.URL],resolve),在v3的时继续使用直接无效,官方提供了defineAsyncComponent方法实现,结合vite的摇树优化,可以使用import.meta.glob('../contentChild//')先去加载所有页面的()=>import(url),再使用vue的components接收一个对象,用一个对象去给import.meta.glob每一个函数套上一层defineAsyncComponent方法,部分代码:

import Error404 from '@/components/error/404.vue'
import PageLoading from '@/components/loading.vue'
import networkError from '@/components/error/networkError.vue'
const Components: any = {
        Error404,
        networkError,
}
const handleComName = function (url: string, sign: boolean): string {
        const Name = url
                .substring(url.substring(0, url.lastIndexOf('/')).lastIndexOf('/'))
                .replace(/\//gi, '')
                .replace('.vue', '')

        return sign ? Name : Components[Name] ? Name : 'Error404'
}
// @ts-ignore
const Modeuls: any = import.meta.glob('../contentChild/*/*')
const AsyncComOptions = {
        // 工厂函数
        loader: null,
        // 加载异步组件时要使用的组件
        loadingComponent: PageLoading,
        // 加载失败时要使用的组件
        errorComponent: networkError,
        // 在显示 loadingComponent 之前的延迟 | 默认值:200(单位 ms)
        // delay: 20000,
        // 如果提供了 timeout ,并且加载组件的时间超过了设定值,将显示错误组件
        // 默认值:Infinity(即永不超时,单位 ms)
        timeout: 3000,
        // 定义组件是否可挂起 | 默认值:true
        // suspensible: false,
        /**
         *
         * @param {*} error 错误信息对象
         * @param {*} retry 一个函数,用于指示当 promise 加载器 reject 时,加载器是否应该重试
         * @param {*} fail  一个函数,指示加载程序结束退出
         * @param {*} attempts 允许的最大重试次数
         */
        onError(error: any, retry: any, fail: any, attempts: any) {
                if (error.message.match(/fetch/) && attempts <= 3) {
                        // 请求发生错误时重试,最多可尝试 3 次
                        retry()
                } else {
                        // 注意,retry/fail 就像 promise 的 resolve/reject 一样:
                        // 必须调用其中一个才能继续错误处理。
                        fail()
                }
        },
}
Object.keys(Modeuls).map((item) => {
        const com = handleComName(item, true)
        Components[com] = defineAsyncComponent({
                ...AsyncComOptions,
                loader: Modeuls[item],
        })
})
export default defineComponent({
name: 'Tabs',
components: Components,
    setup(){
            // 标签页集合
	const tabs: Array<IMenu> = reactive([])
            // 当前激活的标签页
	const currentTabValue = ref<string | null>(null)
            const addTab = (data: IMenu) => {
        const ModelValue = data.isDialog ? 'CDID' : 'URL'
		const CurrentURL:string | null = data[ModelValue] as string | null
        console.log(CurrentURL);
		// 判断是否在标签栏里已经打开了点击的菜单
		const { is, index } = Lookup(tabs, CurrentURL, ModelValue)
		// !is && tabs.push(Object.freeze(data))
		if (is) {
			currentTabValue.value = CurrentURL
			tabs[index] = data
		} else {
			// 如果没找到页面,则判断是否有菜单名称,如果没有菜单名称,则不是点击左侧菜单进来,而是子页面主动跳转,跳转中可能存在url在数据库被改动,而前端未做同步,那么就跳转到错误页面
			const Tab = data.CDMC
				? data
				: {
						CDID: '404',
						CDMC: '路径错误',
						CZLX: '404',
						FJID: 0,
						ID: '404',
						URL: '../../components/error/networkError.vue',
						XH: '404',
						child: null,
						menuHide: true,
				  }
			tabs.push(Object.freeze(Tab))
			currentTabValue.value = CurrentURL
                        data.isDialog && ctx.emit('update:modelValue', Tab)
		}
	}
            // 点击一个标签页
	const activeTab = (tab: any) => {
		ctx.emit('update:modelValue', tabs[tab.index])
	}
            watch(
		() => props.modelValue,
		(val) => {
			currentTabValue.value = val.URL
			addTab(val)
		}
	)
        const currentComNameFN = function (url: string): string {
                const tempUrl = url.substring(url.indexOf("\/")+1,url.length)
        
                return tempUrl.substring(0,tempUrl.indexOf("\/"))
	}
        return {
                currentTabValue,
		tabs,
		currentComNameFN,
	}
    }
})


<el-tabs
    class="tabs-box"
    v-model="currentTabValue"
    type="card"
    @tab-remove="removeTab"
    @tab-click="activeTab"
>
    <el-tab-pane
        v-for="(item, index) in tabs"
        :key="item.CDID"
        :name="item.URL.indexOf('/com/') === -1 ? item.URL : item.CDID"
        :closable="index !== 0"
    >
            <div class="contentChild-box">
                    <keep-alive>
                            <suspense>
                                <component
                                     :key="item.CDID"   
                                     :is="currentComNameFN(item.URL)"
                                     v-bind:formoptions="item.formoptions"
                                     v-bind:formdata="item.formdata"
                                     v-bind:tabid="item.CDID"
                                    ></component>
                            </suspense>
                    </keep-alive>
            </div>
    </el-tab-pane>
</el-tabs>

代码没有贴完,有一些删改,大致就是这样。