vue3进阶用法之通过调用函数动态加载组件用法及示例

业务场景

假设现在有一个可能在全局任何地方调用的vue组件你会怎么办?非常简单,在app.vue下的router-view同级写上这个组件,在全局中加一个变量v-if判断这个变量就解决了!

tempalte中
<div>
	<router-view />
	<YourComponent v-if="xxxStore.yourComponentVisible" />
</div>
js中
export const showComponent = () => {
	xxxStore.yourComponentVisible = true;
};
export const hideComponent = () => {
	xxxStore.yourComponentVisible = false;
};
某页面中
const handleSubmitForm = async() = >{
	showComponent()
	await reqGetUserInfo()
     hideComponent()
}

完美!!本文结束!!

一点也不好笑,我们进入正题。先看看需要了解到的方法,再来实现这个功能。

主要方法

  • h函数(更准确的名字应该叫createVnode)

从字面意思上来看,简单来说这个东西就是创建虚拟dom用的,在本文里面,它可以将你写的组件,转成vnode,也就是虚拟Dom。这里不做详细赘述。可以看下面文档链接。
api详细用法及信息

  • render函数

白话说就是把你的虚拟dom通过该方法挂载到某一个节点上面。这里不做详细赘述。可以看下面文档链接

api详细用法及信息

实现思路

看过了上面两个方法,先把组件转成虚拟dom,再将虚拟dom挂载到某节点上。那么知道了这些,实现这个功能就很简单了。这里用一个全局loading组件做示例。

简单版实现代码

import UploadLoading from '@/components/UploadLoading/index.vue'
import { render, h, ref, RendererNode, RendererElement, VNode, } from 'vue';
interface ILoadingRef {
    vnode: VNode<RendererNode, RendererElement, {
        [key: string]: any;
    }>;
    div: HTMLDivElement;
}
/**
 * 任何页面中调用该组件进行展示
 * @returns 
 */
export const useUploadLoading = () => {
    const loadingRef = ref<ILoadingRef | null>(null);
    const showLoading = () => {
        // 避免重复挂载组件
        if (loadingRef.value) return
        // 在body上挂载一个div
        const div = document.createElement('div');
        document.body.appendChild(div);
        // 把组件转成虚拟dom
        const vnode = h(UploadLoading);
        // 把这个组件挂载到上面创建的div里面
        render(vnode, div);
        loadingRef.value = { vnode, div };
    };

    const hideLoading = () => {
        if (!loadingRef.value) return
        // 在vue中卸载(unMounted)这个元素
        render(null, loadingRef.value.div);
        // Vue 卸载了这个元素,它仍然存在于 DOM 中,需要手动移除
        document.body.removeChild(loadingRef.value.div);
        loadingRef.value = null;
    };

    return { showLoading, hideLoading };
};

那么问题来了,为什么要走下面两个方法呢?这不是一个意思吗?

render(null, loadingRef.value.div);
document.body.removeChild(loadingRef.value.div);

其实这里看似一样其实另有玄机,render函数让这个组件执行所有的组件卸载生命周期,但元素还在dom上。比如说你的组件中的unMounted中有移除某些事件监听器的方法。要先走这一步让组件按正常流程卸载。

document.body.removeChild才是实际移除这个组件的操作

组件拓展

上面只是说简单实现。那么,现在这个loading组件中,有一个进度条,我需要实时更新这个进度条的进度,怎么办?

那就需要用到h函数的第二个参数了,我们把这个进度条的值传到组件里面。

组件中的props:

const props = defineProps({
	progress: {
		type: Number,
		default: 0
	}
});

核心两行代码:

   const vnode = h(UploadLoading, { progress: progress.value });
   loadingRef.value.vnode.component.props.progress = newProgress;

完整代码:

import UploadLoading from '@/components/UploadLoading/index.vue'
import { render, h, ref, RendererNode, RendererElement, VNode } from 'vue';
interface ILoadingRef {
    vnode: VNode<RendererNode, RendererElement, {
        [key: string]: any;
    }>;
    div: HTMLDivElement;
}
/**
 * 任何页面中调用该组件进行展示
 * @returns 
 */
export const useUploadLoading = () => {
    const loadingRef = ref<ILoadingRef | null>(null);
    const progress = ref(0);
    const showLoading = () => {
        if (!loadingRef.value) {
            const div = document.createElement('div');
            document.body.appendChild(div);
            // 这里第二个参数传组件的props参数
            const vnode = h(UploadLoading, { progress: progress.value });
            render(vnode, div);
            loadingRef.value = { vnode, div };
        }
    };

    const hideLoading = () => {
        if (loadingRef.value) {
            render(null, loadingRef.value.div);
            document.body.removeChild(loadingRef.value.div);
            loadingRef.value = null;
            // 重置进度条
            progress.value = 0
        }
    };
    const updateProgress = (newProgress: number) => {
        progress.value = newProgress;
        if (loadingRef.value && loadingRef.value.vnode.component) {
            // 修改组件中的进度条值
            loadingRef.value.vnode.component.props.progress = newProgress;
        }
    };
    return { showLoading, hideLoading, updateProgress };
};

最后直接引入使用就行,我这里用oss文件分片上传做示例:

  /**
     * 分片上传文件
     */
    async uploadFileByChunk(options: IChunkUploadOptions): Promise<string> {
        const { showLoading, hideLoading, updateProgress } = useUploadLoading();
        const yourOptions = {
            // 获取分片上传进度、断点和返回值。
            progress: (p: number) => {
                updateProgress(Math.floor(p * 100))
            }
            ...省略其他代码
        }
        showLoading()
        try {
            await ossUpload(yourOptions)
            ...省略其他代码
        } finally {
            hideLoading()
        }
    }

总结

vue官方文档那边虽然说了函数的使用方法,但没有列举这种业务场景。这里只是做一个简单的实例,其实可以通过这种方法实现其他更多的功能,比如说modal框等等等

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值