单文件组件
平时我们使用的组件方式大多是单文件组件
一个 Vue 单文件组件 (SFC),通常使用 *.vue 作为文件扩展名,它是一种使用了类似 HTML 语法的自定义文件格式,用于定义 Vue 组件。一个 Vue 单文件组件在语法上是兼容 HTML 的。
每一个 *.vue 文件都由三种顶层语言块构成:<template>、<script> 和 <style>,以及一些其他的自定义块:
- Child.vue
<template>
<div @click="emit('child-emit', 'emit回调参数')">Child: {{ content }}</div>
</template>
<script setup>
defineProps({
content: String
})
defineEmits('child-emit')
</script>
- Parent.vue
<template>
<Child content="这是一个单文件组件" @childEmit="childEmitHandle" />
</template>
<script setup>
import Child from 'Child.vue'
const childEmitHandle = (str) => {
console.log(str)
}
</script>
函数式组件
函数式组件可以使用函数的方式来调用组件,vue3中用createApp来创建
- createApp
创建一个应用实例
- 类型:
function createApp(rootComponent: Component, rootProps?: object): App
-
参数
- 第一个参数是根组件。第二个参数可选,它是要传递给根组件的 props。
-
示例:
import { createApp } from 'vue'
import Child from 'Child.vue'
const app = createApp(Child, {
content: '这是一个单文件组件',
onChildEmit: (str) => childEmitHandle(str)
})
const childEmitHandle = (str) => {
console.log(str)
}
将实例创建到#app元素上
app.mount('#app')
- 函数式声明组件接收emit回调是在第二个参数里,用驼峰式命名前面加上on。例如子组件导出的emit('call-envent'), 函数式组件里面就得用onCallEvent接收
在 Vue 3 中,函数式声明组件具有以下优点:
- 性能优化:不需要实例化,不需要响应式系统进行数据变更的检测,因此渲染性能相对较好,在简单,无状态的组件中性能优势尤为明显
- 代码结构清晰: 结构比较简单,更符合函数式编程的思想,容易进行组件的抽象和复用,可减少复用代码的编写,提高代码的可维护性和可复用性。
- 易于理解和测试:由于是无状态的,其行为更加可预测,没有状态需要跟踪,更容易被理解,维护和测试
用函数式调用组件的方式创建一个MessageBox组件
- /hxMessageBox/module.vue
<template>
<transition name="hx-message__box" @after-leave="onAfterLeave">
<div class="hx-message__box" v-if="isVisible" :style="{width: width}">
<div class="hx-message__box-content">
<div class="hx-message__content-header">
<p class="hx-message__header-title">{{ headerText }}</p>
<p class="hx-message__header-close" v-if="showCancel" @click="cancelHandle"></p>
</div>
<div class="hx-message__content-body">
<p class="hx-message__body-text">{{ content }}</p>
</div>
<div class="hx-message__content-footer">
<hx-button v-if="type !== 'comfirm'" @click="cancelHandle">cancelText</hx-button>
<hx-button class="hx-message__footer-btn" level="primary" @click="successHandle">successText</hx-button>
</div>
</div>
</div>
</transition>
</template>
<script setup lang="ts">
import { ref, computed, nextTick } from 'vue';
import HxButton from '../HxButton.vue';
const props = defineProps({
width: {
default: '400px'
},
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 isVisible = ref(false)
/**
* @description: 遮罩层需要消失动画,两个动画效果必须同时触发,所以关闭时直接调用hide方法
* @return {*}
*/
const onAfterLeave = () => {
props.hide && props.hide()
}
/**
* @description: 显示弹窗
* @return {*}
*/
const show = () => {
isVisible.value = true;
if (props.closeByOverlay) {
props.overlayNode.addEventListener('click', hidden);
}
};
/**
* @description: 隐藏弹窗
* @return {*}
*/
const hidden = () => {
isVisible.value = false;
onAfterLeave()
if (props.closeByOverlay) {
props.overlayNode.removeEventListener('click', hidden);
}
};
const successHandle = () => {
if (!props.holdOnFn) {
props.successPromise && props.successPromise()
nextTick(() => {
hidden()
})
} else {
props.holdOnFn()
nextTick(() => {
hidden()
})
}
}
const cancelHandle = () => {
props.cancelPromise && props.cancelPromise();
nextTick(() => {
hidden();
});
}
/**
* @description: 将方法暴露出去
* @return {*}
*/
defineExpose({
show
});
</script>
<style lang="scss">
.hx-message__dialog {
position: fixed;
z-index: 999;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
.hx-message__overlay {
background: rgba(0,0,0,.5);
opacity: 0;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 100;
}
.hx-message__box-overlay-show-to {
animation: opacity-show .3s linear forwards;
}
@keyframes opacity-show {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
.hx-message__box-overlay-leave-to {
animation: opacity-leave .3s linear forwards;
}
@keyframes opacity-leave {
0% {
opacity: 1;
}
100% {
opacity: 0;
}
}
.hx-message__box {
position: fixed;
top: 45%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 99;
max-width: 92%;
overflow: hidden;
&-content {
width: 100%;
padding: 24px;
border-radius: 10px;
background-color: #fff;
.hx-message__content-header {
display: flex;
justify-content: space-between;
align-items: center;
.hx-message__header-title {
flex: 1;
font-size: 18px;
color: #1A1A1A;
line-height: 24px;
}
.hx-message__header-close {
flex-shrink: 0;
margin: 0 0 0 24px;
position: relative;
display: inline-block;
width: 16px;
height: 16px;
cursor: pointer;
&::before,
&::after {
content: '';
position: absolute;
height: 1px;
background: black;
width: 100%;
top: 50%;
left: 50%;
}
&::before {
transform: translate(-50%, -50%) rotate(-45deg);
}
&::after {
transform: translate(-50%, -50%) rotate(45deg);
}
}
}
.hx-message__content-body {
display: flex;
justify-content: center;
width: 100%;
padding: 24px 0 34px;
.hx-message__body-text{
max-width: 100%;
width: 100%;
text-align: left;
font-size: 14px;
color: #1A1A1A;
line-height: 18px;
}
}
.hx-message__content-footer {
display: flex;
justify-content: flex-end;
.hx-message__footer-btn {
margin-left: 15px
}
}
}
}
</style>
- hxMessageBox/index.ts
import { createApp } from "vue";
import hxMessageBox from "./module.vue"
const hxMessageBoxDom = (options = {}) => {
// 创建弹窗元素节点
const rootNode:HTMLElement = document.createElement('div')
rootNode.className = `hx-message__dialog`
document.body.appendChild(rootNode)
// 创建遮罩层
let overlayNode = createOverlay(rootNode);
function createOverlay(rootNode) {
const overlayNode:HTMLElement = document.createElement('div')
overlayNode.classList.add('hx-message__overlay')
rootNode.appendChild(overlayNode)
setTimeout(() => {
overlayNode.classList.add('hx-message__box-overlay-show-to')
}, 100)
return overlayNode
}
const hide = function() {
// 显示移出动画
overlayNode.classList.add('hx-message__box-overlay-leave-to');
// 卸载已挂载的应用实例
setTimeout(() => {
app.unmount();
document.body.contains(rootNode) && document.body.removeChild(rootNode);
}, 500);
}
// 创建应用实例(第一个参数是根组件。第二个参数可选,它是要传递给根组件的 props)
const app = createApp(hxMessageBox, {
...options,
hide,
overlayNode
});
// 将应用实例挂载到创建的 DOM 元素上
return app.mount(overlayNode);
}
/**
* @description: 显示弹窗
* @return {*}
* @param {*} options
*/
const showHxMessageBoxDom = (options = {}) => {
return new Promise<void>((resolve, reject) => {
options['successPromise'] = () => {
resolve();
};
options['cancelPromise'] = () => {
reject();
};
const popres: any = hxMessageBoxDom(options);
popres.show();
});
};
// 注册插件app.use()会自动执行install函数
hxMessageBoxDom.install = app => {
// 注册全局属性,类似于 Vue2 的 Vue.prototype
app.config.globalProperties.$hxMessageBox = options => showHxMessageBoxDom(options);
};
// 定义confirm方法用于直接调用
hxMessageBoxDom.confirm = options => showHxMessageBoxDom(options);
export default hxMessageBoxDom;
- main.ts
- 在main.ts中全局注册一下
import { createApp } from 'vue'
import hxMessageBox from './lib/hxMessageBox/index'
const app = createApp(App)
app.use(hxMessageBox)
- 调用
<demo>常规用法</demo>
<template>
<div>
<hx-button @click="open">点击</hx-button>
<hx-button @click="open2">只有确定框</hx-button>
<hx-button @click="open3">点击遮罩关闭弹框</hx-button>
</div>
</template>
<script setup lang="ts">
import { HxButton, HxMessageBox } from 'hx-gulu-ui';
const open = () => {
HxMessageBox.confirm({
content: '消息内容',
successText: '确定',
cancelText: '取消',
headerText: '头部标题',
})
.then(() => {
console.log('success')
})
.catch(() => {
console.log('cancel')
})
}
const open2 = () => {
hxMessageBox.confirm({
content: 'confirm 弹框',
successText: '确定',
headerText: '头部标题',
type: 'comfirm',
})
.then(() => {
console.log('success')
})
}
const open3 = () => {
hxMessageBox.confirm({
content: '点击遮罩层可以关闭',
successText: '确定',
cancelText: '取消',
headerText: '头部标题',
closeByOverlay: true,
})
.then(() => {
console.log('success')
})
.catch(() => {
console.log('cancel')
})
}
</script>