函数式组件是没有管理任何状态,也没有监听任何传递给它的状态,也没有生命周期方法,它只是一个接受一些 prop 的函数,下面这篇文章主要给大家介绍了关于Vue3封装全局函数式组件的知识点以及实战案例,需要的朋友可以参考下。
Sayings:
Click to open the Message Box
import { ElMessage, ElMessageBox } from 'element-plus'
const open = () => {
ElMessageBox.confirm(
'proxy will permanently delete the file. Continue?',
'Warning',
{
confirmButtonText: 'OK',
cancelButtonText: 'Cancel',
type: 'warning',
}
)
.then(() => {
ElMessage({
type: 'success',
message: 'Delete completed',
})
})
.catch(() => {
ElMessage({
type: 'info',
message: 'Delete canceled',
})
})
}
01——什么是函数式组件?
到目前为止,前端组件类型分为两类:1、声明式组件;2、函数式组件。
很遗憾的一点是:我面试过非常多的说自己擅长组件化开发的前端工程师,在问到函数式组件时却表示没有开发过此类组件同时对原理也是未曾了解过。
一、声明式组件
Click to open the Message Box
import { ElButton } from 'element-plus'
还包括我们自己在项目中写的自定义名称组件且在.vue文件里引用其他.vue文件的就是声明式组件,例如:
import GtButton from '@/components/GtButton.vue';
。
二、函数式组件
ElMessage.error('Oops, this is a error message.')
function createApp(rootComponent: Component, rootProps?: object): App
第一个参数是根组件。第二个参数可选,它是要传递给根组件的 props。
interface App {
mount(rootContainer: Element | string): ComponentPublicInstance
}
参数可以是一个实际的 DOM 元素或一个 CSS 选择器 (使用第一个匹配到的元素)。返回根组件的实例。
如果该组件有模板或定义了渲染函数,它将替换容器内所有现存的 DOM 节点。否则在运行时编译器可用的情况下,容器元素的 innerHTML 将被用作模板。
在 SSR 激活模式下,它将激活容器内现有的 DOM 节点。如果出现了激活不匹配,那么现有的 DOM 节点将会被修改以匹配客户端的实际渲染结果。
对于每个应用实例,mount() 仅能调用一次。
interface App {
unmount(): void
}
利用这个基础的 Vue 构造器,能创建Vue子类实例,然而在 Vue3 官方删除了这个方法,但是也提供了新的api:createApp 给我们使用,利用 createApp 就能创建 Vue 应用实例了。
02——创建自定义函数式组件——确认弹窗组件
下面将创建一个自定义的确认弹窗组件,先展示一下调用后的效果:
首先我们在 components 文件夹下新建组件文件夹 gtMessageBox ,然后在 gtMessageBox 文件夹中新建两个文件,一个 module.vue 文件用来正常写 vue 的组件【定义好接收的参数、组件的样式】;一个 index.ts 文件用来作为组件的引用出口同时将 module.vue 转化为DOM实例进行展示。
一、封装 GtMessageBox 组件
import { ref, computed, nextTick } from 'vue';
import gtButton from '@/components/gtButton/index.vue';
const props = defineProps({
width: {
default: 400
},
successText: {
type: String,
default: '确认'
},
cancelText: {
type: String,
default: '取消'
},
headerText: {
type: String,
default: '提示'
},
content: {
type: String,
default: ''
},
// 成功回调函数
successPromise: {
type: Function
},
// 失败回调函数
cancelPromise: {
type: Function
},
hide: {
type: Function
},
// 是否需要右上角关闭按钮
showCancel: {
type: Boolean,
default: true
},
// 弹窗类型:default 默认显示确认、取消/confirm 仅显示确认
type: {
type: String,
default: 'default'
},
// 默认点击遮罩层不关闭
closeByOverlay: {
type: Boolean,
default: false
},
// 传入调用方法
holdOnFn: {
type: Function
},
overlayNode: {
type: Object
},
});
const isShow = ref(false); // 弹窗控制
const messageBoxWidth = computed(
() =>
((parseInt(props.width.toString()) / 1920) * document.documentElement.clientWidth).toFixed(3) +
'px'
); // 宽度控制
/**
* @description: 遮罩层需要消失动画,两个动画效果必须同时触发,所以关闭时直接调用hide方法
* @return {*}
*/
const onAfterLeave = () => {
// props.hide && props.hide();
};
/**
* @description: 显示弹窗
* @return {*}
*/
const show = () => {
isShow.value = true;
if (props.closeByOverlay) {
props.overlayNode.addEventListener('click', hidden);
}
};
/**
* @description: 隐藏弹窗
* @return {*}
*/
const hidden = () => {
isShow.value = false;
props.hide();
if (props.closeByOverlay) {
props.overlayNode.removeEventListener('click', hidden);
}
};
const successHandle = () => {
if (!props.holdOnFn) {
props.successPromise();
nextTick(() => {
hidden();
});
} else {
props.holdOnFn();
// nextTick(() => {
// hidden();
// });
}
};
const cancelHandle = () => {
props.cancelPromise();
nextTick(() => {
hidden();
});
}
/**
* @description: 将方法暴露出去
* @return {*}
*/
defineExpose({
show
});
.gt-message-box{
position: fixed;
top: 45%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 99;
.message-box-content{
width: 100%;
padding: 30px;
border-radius: 14px;
background: #FFFFFF;
.content-header{
display: flex;
justify-content: space-between;
align-items: center;
.header-title{
flex: 1;
font-size: 18px;
color: #1A1A1A;
line-height: 24px;
}
.header-close{
flex-shrink: 0;
padding: 0 0 0 24px;
.gticon-a-rongqi69{
font-size: 24px;
color: #949494;
}
}
}
.content-body{
display: flex;
justify-content: center;
width: 100%;
padding: 30px 0 40px;
.body-text{
max-width: 100%;
font-size: 14px;
color: #1A1A1A;
line-height: 18px;
}
}
.content-footer{
display: flex;
justify-content: flex-end;
.footer-btn{
margin-left: 20px;
}
}
}
}
/**
* @description: 全局样式
* @return {*}
*/
.gt-message-box-overlay {
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
z-index: 100;
background: rgba(0, 0, 0, 0.1);
transition: opacity 0.3s ease-out;
}
.gt-message-box-overlay-enter-from,
.gt-message-box-overlay-leave-to {
opacity: 0;
}
这是最关键的步骤,在 Vue2 的时候封装函数式组件使用的是 Vue.extend
,利用这个基础的 Vue 构造器,能创建Vue子类实例,然而在 Vue3 官方删除了这个方法,但是也提供了新的api: createApp
,利用 createApp 就能创建 Vue 应用实例了。代码如下:
这部分在 index.ts 文件中:
import { createApp } from 'vue';
import GtMessageBox from '@/components/gtMessageBox/module.vue';
/**
* @description: 创建自定义vue组件实例并挂载到Dom上
* @return {*}
* @param {*} options
*/
const gtMessageBoxDom = (options = {}) => {
// 创建遮罩层
let overlayNode = createOverlay();
// 创建弹窗元素节点
const rootNode: HTMLElement = document.createElement('div');
// 在body标签内部插入此元素
rootNode.className = `gt-message-dialog`;
rootNode.style.position = 'fixed';
rootNode.style.zIndex = overlayNodeZIndex + '';
document.body.appendChild(rootNode);
const hide = function () {
// 显示移除动画
overlayNode.classList.add('gt-message-box-overlay-leave-to');
setTimeout(() => {
overlayNode.classList.remove('gt-message-box-overlay-leave-to');
deleteOverlay(overlayNode);
}, 300);
// 卸载已挂载的应用实例
setTimeout(() => {
app.unmount();
document.body.contains(rootNode) && document.body.removeChild(rootNode);
}, 300);
};
// 创建应用实例(第一个参数是根组件。第二个参数可选,它是要传递给根组件的 props)
const app = createApp(GtMessageBox, {
...options,
hide,
overlayNode
});
// 将应用实例挂载到创建的 DOM 元素上
return app.mount(rootNode);
};
/**
* @description: 显示弹窗
* @return {*}
* @param {*} options
*/
const showGtMessageBoxDom = (options = {}) => {
return new Promise((resolve, reject) => {
options['successPromise'] = () => {
resolve();
};
options['cancelPromise'] = () => {
reject();
};
const popres: any = gtMessageBoxDom(options);
popres.show();
});
};
// 注册插件app.use()会自动执行install函数
gtMessageBoxDom.install = app => {
// 注册全局属性,类似于 Vue2 的 Vue.prototype
app.config.globalProperties.$GtMessageBox = options => showGtMessageBoxDom(options);
};
// 定义confirm方法用于直接调用
gtMessageBoxDom.confirm = options => showGtMessageBoxDom(options);
export default gtMessageBoxDom;
// Vue3封装全局函数式组件
import GtMessageBox from '@/components/gtMessageBox/index';
app.use(GtMessageBox); // Vue3封装全局函数式组件
①:为什么采用调用函数方法的方式去控制显隐
答:目的是为了那个显示与消失的动画效果,当组件创建后需要组件内 ”isShow“ 产生变化才能触发 的动画效果,所以这里写了show函数方法。
②:函数式组件的这两个文件之间的联系
答:简单来说,ts 文件传参数及函数给vue文件,均可在 createApp 的第二个参数中传递,vue文件相当于子组件,使用props的方式接收;vue文件传值及函数给ts文件,可以通过 defineExpose 方法暴露出去,ts文件中在应用实例创建完成后,就能拿到暴露出来的属性及方法。
import { getCurrentInstance } from 'vue';
// 获取当前实例,在当前实例相当于 vue2 中的 this
const { proxy }: any = getCurrentInstance();
// 最简单的调用方式,即可出来开头所展示的效果
proxy.$GtMessageBox({
content: '此审批已经被删除,请刷新页面!'
});
// 传递自定义参数,与 module.vue 文件接收的参数对应
setTimeout(() => {
proxy.$GtMessageBox({
headerText: '警告',
content: 'Hello World'
})
.then(() => {
console.log(111)
})
.catch(() => {
console.log(222)
})
}, 2000);
import GtMessageBox from "@/components/gtMessageBox/index";
GtMessageBox.confirm({
content: '此审批已经被删除,请刷新页面!',
holdOnFn: () => {
GtMessageBox.confirm({
content: '此审批已经被删除,请刷新页面!',
type: 'confirm'
})
.then(() => {
console.log(333)
// 卸载已打开的所有弹窗
GtMessageBox.hideAll()
})
}
})
.then(() => {
console.log(111)
})
.catch(() => {
console.log(222)
})
上面封装的 GtMessageBox 组件在创建多个实例的时候,它们之间是互不干扰的,不会存在组件参数异常的情况。那么实际观察 DOM 元素我们会发现其在 DOM 上是存在多个的,只不过当多次调用的时候,后面的会把前面还没消失的 GtMessageBox 覆盖了,这样效果可能不那么友好。那么就存在两个优化方向:一是当后续出现 GtMessageBox 的时候结束掉前面出现的 GtMessageBox ,二是调整后续 GtMessageBox 出现的层级位置。
第一种解决办法其实就是运用了 单例模式;
怎么去结束前面出现的 GtMessageBox 呢,我们只需要确保全局只渲染一个 GtMessageBox 弹窗就行,所以可以使用单例模式,单例模式即一个类只能有一个实例。类似Vant的Toast组件,其默认采用了单例模式,即同一时间只会存在一个,这种做法应该是普遍的弹窗做法。
第二种解决办法实现多个确认弹窗其实就是设置一个初始的层级变量,每创建一个确认弹窗组件变量就自增加1,然后将新的层级变量赋值给新的确认弹窗,这样就实现了多个弹窗共存,但是相应的也需要提供一个卸载所有弹窗的方法。
由于本文篇幅有限所以就不展开了,在下一篇文章会进行详细的讲解及代码示例。
03——总结
以上就是全部内容,本文简单介绍了 Vue3 函数式组件的封装方法,将其以插件的方式使用app.use() 方法安装在 Vue 上,使其作为全局功能的工具,这就是 Vue3 中逻辑复用的插件 (Plugins) 写法。
1、本站所有资源均从互联网上收集整理而来,仅供学习交流之用,因此不包含技术服务请大家谅解!
2、本站不提供任何实质性的付费和支付资源,所有需要积分下载的资源均为网站运营赞助费用或者线下劳务费用!
3、本站所有资源仅用于学习及研究使用,您必须在下载后的24小时内删除所下载资源,切勿用于商业用途,否则由此引发的法律纠纷及连带责任本站和发布者概不承担!
4、本站站内提供的所有可下载资源,本站保证未做任何负面改动(不包含修复bug和完善功能等正面优化或二次开发),但本站不保证资源的准确性、安全性和完整性,用户下载后自行斟酌,我们以交流学习为目的,并不是所有的源码都100%无错或无bug!如有链接无法下载、失效或广告,请联系客服处理!
5、本站资源除标明原创外均来自网络整理,版权归原作者或本站特约原创作者所有,如侵犯到您的合法权益,请立即告知本站,本站将及时予与删除并致以最深的歉意!
6、如果您也有好的资源或教程,您可以投稿发布,成功分享后有站币奖励和额外收入!
7、如果您喜欢该资源,请支持官方正版资源,以得到更好的正版服务!
8、请您认真阅读上述内容,注册本站用户或下载本站资源即您同意上述内容!
原文链接:https://www.shuli.cc/?p=16762,转载请注明出处。
评论0