这两天的任务是把一批做好的vue3组件放在vue2项目中使用,将组合式api分散开有一些零散的技巧,所以写一篇转化笔记以供大家参考
先上vue3一个组件的示例代码
<template>
<div ref="GForms" :style="{background: props.background, border: '1px solid ' + props.borderColor, 'backdrop-filter': props.backBlur? 'blur(10px)': 'blur(0px)'}" style="min-width: 200px; min-height: 60px; padding: 0; margin: 0; position: relative; transition: width .8s, height .8s;">
<!-- 四个角 -->
<div v-if="!props.corner" :style="{background: props.cornerColor}" style="position: absolute; left: -1px; top: -1px; width: calc(100% + 2px); height: 2px;"></div>
<div v-if="props.corner" style="position: absolute; left: -1px; top: -1px; transform: rotateZ(0deg);">
<div :style="{background: props.cornerColor}" style="width: 6px; height: 2px; position: absolute;"></div>
<div :style="{background: props.cornerColor}" style="width: 6px; height: 2px; position: absolute; transform: rotateZ(90deg); left: -2px; top: 2px;"></div>
</div>
<div style="position: absolute; left: -1px; bottom: -1px; transform: rotateZ(270deg);">
<div :style="{background: props.cornerColor}" style="width: 6px; height: 2px; position: absolute;"></div>
<div :style="{background: props.cornerColor}" style="width: 6px; height: 2px; position: absolute; transform: rotateZ(90deg); left: -2px; top: 2px;"></div>
</div>
<div v-if="props.corner" style="position: absolute; right: -1px; top: -1px; transform: rotateZ(90deg);">
<div :style="{background: props.cornerColor}" style="width: 6px; height: 2px; position: absolute;"></div>
<div :style="{background: props.cornerColor}" style="width: 6px; height: 2px; position: absolute; transform: rotateZ(90deg); left: -2px; top: 2px;"></div>
</div>
<div style="position: absolute; right: -1px; bottom: -1px; transform: rotateZ(-180deg);">
<div :style="{background: props.cornerColor}" style="width: 6px; height: 2px; position: absolute;"></div>
<div :style="{background: props.cornerColor}" style="width: 6px; height: 2px; position: absolute; transform: rotateZ(90deg); left: -2px; top: 2px;"></div>
</div>
<!-- 标题栏 @mouseup="gFormsMouseup" @mousemove="gFormsMousemove"-->
<div v-if="props.showTitle" @mousedown="gFormsMousedown" :style="{cursor: props.move? 'move': 'default'}" style="user-select: none; padding: 4px 6px; display: flex; flex-direction: row; flex-wrap: nowrap; justify-content: space-between; align-items: center;">
<div style="margin-top: 2px; display: flex; flex-direction: row; flex-wrap: nowrap; align-items: center;">
<div style="font-size: 16px; font-weight: bold; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">{{props.title}}</div>
<img :src="data.icon" alt="logo" style="margin: -3px 0 0 3px;"/>
</div>
<div @click.stop="gFormsCloseWindows" class="g-forms-close-btn" style="position: relative; width: 22px; height: 22px;">
<div class="g-forms-close-btn-bg" style="position: relative; width: 20px; height: 20px; margin: 1px;">
<div style="position: absolute; width: 14px; height: 2px; transform: rotateZ(45deg); transform-origin: center 50%; background: #fff; top: 9px; left: 3px;"></div>
<div style="position: absolute; width: 14px; height: 2px; transform: rotateZ(-45deg); transform-origin: center 50%; background: #fff; top: 9px; left: 3px;"></div>
</div>
<div style="position: absolute; left: -1px; top: -1px; width: 2px; height: 2px; background: #fff;"></div>
<div style="position: absolute; left: -1px; bottom: -1px; width: 2px; height: 2px; background: #fff;"></div>
<div style="position: absolute; right: -1px; top: -1px; width: 2px; height: 2px; background: #fff;"></div>
<div style="position: absolute; right: -1px; bottom: -1px; width: 2px; height: 2px; background: #fff;"></div>
</div>
</div>
<!-- 分割条 -->
<div v-if="props.showTitle" style="display: flex; flex-direction: row; flex-wrap: nowrap; align-items: center; padding: 0 4px;">
<div style="width: 90%; background: #4F728E; height: 3px;"></div>
<div style="width: 10%; background: #DEE3E9; height: 3px; margin-left: 2px;"></div>
</div>
<slot></slot>
</div>
</template>
<script lang="ts" setup>
// 边框
import { defineProps, reactive, defineEmits, ref, onMounted } from 'vue'
const props = defineProps({
title: { type: String, default: "G-Forms" }, // 窗口标题
showTitle: { type: Boolean, default: true }, // 显示标题
move: { type: Boolean, default: true }, // 是否可以移动
inArea: { type: Boolean, default: true }, // 是否在区域内移动
corner: { type: Boolean, default: true }, // 四个角是否为尖角
cornerColor: { type: String, default: "#FFF" }, // 四角装饰颜色
backBlur: { type: Boolean, default: true }, // 开启背景模糊
background: { type: String, default: "#001820ab" }, // 背景颜色
borderColor: { type: String, default: "#2F739A" }, // 边框线条颜色
})
const GForms = ref<HTMLElement | null>()
let gflag = ref(false)
const data = reactive({
icon: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACUAAAAZCAYAAAC2JufVAAABYElEQVRIx2P4//8/w2DDDKOOGnXUiHKUqW/cAyD+j4Qb5m478QCI/yPhBkrU/b/k+wCI/yPhBkKOckAz5H/5hEUpaJb9jy5uSSFX3bZFwSlojgJhA7zRB9Q4Ac2gC0ALJqBZeIESdUBHTEBz1AVCjhJAD3bHyKwO9OiZuGpPB7nqfGNjOghFI0Z8DoZoxJr6BzoacTlqQKMRZ1kB1JiAHuxACxLQo4cSdUBHJGBEI4GQ+oBsgFN0TiXQ8A/IFk1YsauSXHX+cTGVQEd8QHNUAT5HHUDz1Qag4QfQfL+BEnVABxxAc9AGfLmvAc2AB9M3HOpAs+iBdXBKB7nqPp/y60BzEChtCeAqpwLQDPhQN31lIZpFH0JyagvJVXdkdVAhmoM+oJfqyA5SQI/3oKzqEvT00TBzdQm56nKKIkuwpKMEfHXfBTRfLQCVMWi+X0CJOnBZhOqgBaPtqVFHjVhHAQDOW7xzUODHCgAAAABJRU5ErkJggg==",
// 鼠标位置
gfMX: 0,
gfMY: 0,
// 偏移量
deltaX: 0,
deltaY: 0,
// 页面尺寸
pageW: 0,
pageH: 0
})
const emit = defineEmits(['close'])
onMounted(() => {
data.pageW = window.innerWidth
data.pageH = window.innerHeight
window.onresize = () => {
data.pageW = window.innerWidth
data.pageH = window.innerHeight
}
if (data.deltaX != 0 || data.deltaY != 0) setPosition()
})
// 设置窗体位置
const setPosition = () => setTimeout(() => {
if (GForms.value == null || GForms.value == undefined) return;
if (props.inArea) {
if (data.deltaX < 1) data.deltaX = 1
if (data.deltaY < 80) data.deltaY = 80
if (data.deltaX + GForms.value?.offsetWidth > data.pageW) data.deltaX = data.pageW - GForms.value?.offsetWidth
if (data.deltaY + GForms.value?.offsetHeight > data.pageH) data.deltaY = data.pageH - GForms.value?.offsetHeight
}
GForms.value.style.top = data.deltaY + "px"
GForms.value.style.left = data.deltaX + "px"
}, 0)
// 关闭方法
const gFormsCloseWindows = () => {
emit('close')
}
// 窗体移动方法
const gFormsMousedown = (e:any) => {
if (!props.move) return
gflag.value = true
data.gfMX = e.offsetX
data.gfMY = e.offsetY
// 防抖
if (data.deltaX != 0 || data.deltaY != 0) setPosition()
// 移动过程
document.onmousemove = (e: any) => {
if (!gflag.value) return;
data.deltaX = e.clientX - data.gfMX
data.deltaY = e.clientY - data.gfMY
setPosition()
}
// 鼠标抬起
document.onmouseup = (e: any) => {
setPosition()
gflag.value = false
document.onmousemove = null
document.onmouseup = null
}
}
</script>
<style scoped>
.g-forms-close-btn-bg {
background: #2E454C;
}
.g-forms-close-btn:hover .g-forms-close-btn-bg {
background: #4b96a3;
cursor: pointer;
}
.g-forms-close-btn:active .g-forms-close-btn-bg {
background: #2E454C;
cursor: pointer;
}
.div-l-shape-s {
border-style: solid;
border-width: 0 0 3px 3px;
position: relative;
right: -3px;
top: -3px;
background: white;
width: 4em;
height: 4em;
}
</style>
上面是其中一个vue3组件样式表,在vue3的项目中展示效果为下图所示:
组件中还有一些其他的功能都要完整保留,所以现在开始整理:
要将 Vue 3 组件转换为 Vue 2 组件,需要注意一些语法和功能上的差异。下面是我总结的 Vue 3 组件转换为 Vue 2 组件的一般步骤:
1、首先是template里的内容,可以看到在vue3模板里使用属性要这么写:
:style="{background: props.background}" //取出在props里面的值
在 Vue 3 中,使用 props 选项来声明组件的属性,而Vue 3 中的 props 默认是不响应式的,所以可以看到代码中套了一层defineProps方法,defineProps 方法是 Vue 3 中用于声明组件属性的函数。它的作用是定义组件接收的属性,并使其在组件内部成为响应式的。
而在vue2中可以直接使用props里面的值,语法为下面代码:
:style="{background: background}"
export default {
props: {
title: { type: String, default: "G-Forms" }, // 窗口标题
showTitle: { type: Boolean, default: true }, // 显示标题
move: { type: Boolean, default: true }, // 是否可以移动
inArea: { type: Boolean, default: true }, // 是否在区域内移动
corner: { type: Boolean, default: true }, // 四个角是否为尖角
cornerColor: { type: String, default: "#FFF" }, // 四角装饰颜色
backBlur: { type: Boolean, default: true }, // 开启背景模糊
background: { type: String, default: "#001820ab" }, // 背景颜色
borderColor: { type: String, default: "#2F739A" }, // 边框线条颜色
},
}
因为里面的数据都是可以直接使用的
2、在vue2中要去掉<script lang="ts" setup>
里面的lang="ts" setup
书写vue2中的逻辑
删掉import { defineProps, reactive, defineEmits, ref, onMounted } from 'vue'
这类语句因为不需要
3、在vue3中,data的书写方式为下面代码:
const data = reactive({
icon: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACUAAAAZCAYAAAC2JufVAAABYElEQVRIx2P4//8/w2DDDKOOGnXUiHKUqW/cAyD+j4Qb5m478QCI/yPhBkrU/b/k+wCI/yPhBkKOckAz5H/5hEUpaJb9jy5uSSFX3bZFwSlojgJhA7zRB9Q4Ac2gC0ALJqBZeIESdUBHTEBz1AVCjhJAD3bHyKwO9OiZuGpPB7nqfGNjOghFI0Z8DoZoxJr6BzoacTlqQKMRZ1kB1JiAHuxACxLQo4cSdUBHJGBEI4GQ+oBsgFN0TiXQ8A/IFk1YsauSXHX+cTGVQEd8QHNUAT5HHUDz1Qag4QfQfL+BEnVABxxAc9AGfLmvAc2AB9M3HOpAs+iBdXBKB7nqPp/y60BzEChtCeAqpwLQDPhQN31lIZpFH0JyagvJVXdkdVAhmoM+oJfqyA5SQI/3oKzqEvT00TBzdQm56nKKIkuwpKMEfHXfBTRfLQCVMWi+X0CJOnBZhOqgBaPtqVFHjVhHAQDOW7xzUODHCgAAAABJRU5ErkJggg==",
// 鼠标位置
gfMX: 0,
gfMY: 0,
// 偏移量
deltaX: 0,
deltaY: 0,
// 页面尺寸
pageW: 0,
pageH: 0
})
但是在vue2中
data() {
return {
icon: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACUAAAAZCAYAAAC2JufVAAABYElEQVRIx2P4//8/w2DDDKOOGnXUiHKUqW/cAyD+j4Qb5m478QCI/yPhBkrU/b/k+wCI/yPhBkKOckAz5H/5hEUpaJb9jy5uSSFX3bZFwSlojgJhA7zRB9Q4Ac2gC0ALJqBZeIESdUBHTEBz1AVCjhJAD3bHyKwO9OiZuGpPB7nqfGNjOghFI0Z8DoZoxJr6BzoacTlqQKMRZ1kB1JiAHuxACxLQo4cSdUBHJGBEI4GQ+oBsgFN0TiXQ8A/IFk1YsauSXHX+cTGVQEd8QHNUAT5HHUDz1Qag4QfQfL+BEnVABxxAc9AGfLmvAc2AB9M3HOpAs+iBdXBKB7nqPp/y60BzEChtCeAqpwLQDPhQN31lIZpFH0JyagvJVXdkdVAhmoM+oJfqyA5SQI/3oKzqEvT00TBzdQm56nKKIkuwpKMEfHXfBTRfLQCVMWi+X0CJOnBZhOqgBaPtqVFHjVhHAQDOW7xzUODHCgAAAABJRU5ErkJggg==",
// 鼠标位置
gfMX: 0,
gfMY: 0,
// 偏移量
deltaX: 0,
deltaY: 0,
// 页面尺寸
pageW: 0,
pageH: 0,
gflag:false,
GFormsRef:0,
gFormsMouseup: false,
};
},
利用函数式的data存放数据
4、const GForms = ref<HTMLElement | null>()
这是vue3组件中的一句代码,这句话的意思是创建一个响应式引用(ref),其初始值为 null,类型为 HTMLElement 或 null。
在 Vue 3 中,ref 是一个用于创建响应式数据的函数。它接受一个初始值作为参数,并返回一个包含 .value 属性的响应式引用对象。.value 属性用于访问和修改引用的值。当引用的值发生变化时,相关的组件会自动重新渲染。
在上述代码中,GForms 是一个名为 GForms 的变量,它是一个响应式引用。我们可以使用 GForms.value 来访问和修改它的值。在初始状态下,GForms.value 的值为 null。可以通过将其赋值为一个 HTMLElement 对象来将其引用到特定的 DOM 元素。
通过使用 ref 创建的响应式引用,可以轻松地追踪和响应 DOM 元素的变化,并在 Vue 3 组件中进行操作。
而在vue2中,这句话要被拆分为几部分
1、在组件的 data 选项中定义一个名为 GForms 的属性,并将其初始值设置为 null。例如:
data() {
return {
GForms: null,
};
},
2、在模板中使用 ref 属性将 GForms 和相应的 DOM 元素进行绑定。例如:
<div ref="GForms">...</div>
3、在组件的生命周期钩子函数(例如 mounted)中,使用 $refs 来访问 GForms 引用的 DOM 元素,并将其赋值给组件的 GForms 属性。例如:
mounted() {
this.GForms = this.$refs.GForms;
},
在这样的转换后,您可以在组件中使用 this.GForms 来访问和操作引用的 DOM 元素。
4、vue3传值
const emit = defineEmits(['close'])
在Vue 3中,const emit = defineEmits(['close'])
这句话的作用是定义一个名为emit的常量,它使用defineEmits函数来声明组件的自定义事件。
defineEmits是Vue 3中的一个全局辅助函数,用于声明组件可以触发的自定义事件。通过传递一个字符串数组,你可以定义组件可以发出的事件名称。
在我的例子中,['close']
是一个包含单个字符串'close'
的数组,它表示组件可以触发名为'close'
的自定义事件。通过使用defineEmits(['close'])
,可以确保组件在使用$emit方法触发’close’事件时,不会产生警告。
在组件中,可以使用emit常量来触发声明的自定义事件,例如:
<script>
import { defineEmits } from 'vue';
const emit = defineEmits(['close']);
export default {
methods: {
handleClose() {
// 触发 'close' 事件
emit('close');
},
},
};
</script>
在上述代码中,handleClose方法通过调用emit('close')
来触发’close’事件。这样,其他使用这个组件的地方可以监听该事件并执行相应的逻辑。
在我的代码中:
<div @click.stop="gFormsCloseWindows"/>
点击触发gFormsCloseWindows
函数,
const gFormsCloseWindows = () => {
emit('close')
}
这样的话就可以在组件外的父组件上进行接收close,
在app组件中
@close="handleClose"
之后在data中定义
data() {
return {
showChildComponent: true
};
},
在methods中定义
methods: {
handleClose() {
// 执行关闭操作
this.showChildComponent = false;
}
}
前提是在gForm组件中要预留
v-if="showChildComponent"
就可以实现此功能
5、生命周期转化
在vue3中
onMounted(() => {
data.pageW = window.innerWidth
data.pageH = window.innerHeight
window.onresize = () => {
data.pageW = window.innerWidth
data.pageH = window.innerHeight
}
if (data.deltaX != 0 || data.deltaY != 0) setPosition()
})
而在vue2中进行相应的转化
mounted() {
this.pageW = window.innerWidth;
this.pageH = window.innerHeight;
window.onresize = () => {
this.pageW = window.innerWidth;
this.pageH = window.innerHeight;
};
if (this.deltaX !== 0 || this.deltaY !== 0) {
this.setPosition();
}
},
6、methods转化
vue3代码示例
// 窗体移动方法
const gFormsMousedown = (e:any) => {
if (!props.move) return
gflag.value = true
data.gfMX = e.offsetX
data.gfMY = e.offsetY
// 防抖
if (data.deltaX != 0 || data.deltaY != 0) setPosition()
// 移动过程
document.onmousemove = (e: any) => {
if (!gflag.value) return;
data.deltaX = e.clientX - data.gfMX
data.deltaY = e.clientY - data.gfMY
setPosition()
}
// 鼠标抬起
document.onmouseup = (e: any) => {
setPosition()
gflag.value = false
document.onmousemove = null
document.onmouseup = null
}
}
转化为vue2
methods: {
gFormsCloseWindows() {
this.$emit('close');
},
// 设置窗体位置
setPosition() {
const GFormsRef = this.$refs.GForms;
setTimeout(() => {
if (this.GFormsRef == null || this.GFormsRef == undefined) return;
// 在判断时应该使用 this.GFormsRef,而不是 this.GForms
if (this.inArea) {
if (this.deltaX < 1) this.deltaX = 1;
if (this.deltaY < 1) this.deltaY = 1;
if (this.deltaX + this.GFormsRef.offsetWidth > this.pageW) {
this.deltaX = this.pageW - this.GFormsRef.offsetWidth;
}
if (this.deltaY + this.GFormsRef.offsetHeight > this.pageH) {
this.deltaY = this.pageH - this.GFormsRef.offsetHeight;
}
}
GFormsRef.style.top = this.deltaY-60 + "px";
GFormsRef.style.left = this.deltaX + "px";
// 注意使用 this.GFormsRef.style 来设置元素的样式,而不是 this.GForms.style
},0);
},
// 窗体移动方法
gFormsMousedown(e) {
if (!this.move) return;
this.gflag = true;
this.gfMX = e.offsetX;
this.gfMY = e.offsetY;
// 防抖
if (this.deltaX !== 0 || this.deltaY !== 0) {
this.setPosition();
}
// 移动过程
document.onmousemove = (e) => {
if (!this.gflag) return;
// console.log(e.clientX);
// console.log(e.clientY);
// e.clientX表示鼠标当前的水平坐标
this.deltaX = e.clientX - this.gfMX;
this.deltaY = e.clientY - this.gfMY;
this.setPosition();
}
// 鼠标松开
},
handleMouseUp() {
this.setPosition();
this.gflag = false;
document.onmousemove = null;
document.onmouseup = null;
}
},
}
大致就是这些,其实熟悉了之后组件之间相互转化就很快了,希望对大家有所帮助