前言
系列前几个组件都是用vue2编写的,这次用最新的vue3编写, vue3也出了一段时间,公司也不会牟然换版本,所以很少实践的机会,那就只能够自己写一个组件实践下,看了一下vue3的文档,最大变化,就是setup函数和Composition Api这块,这次我主要也是说说这部分的使用过程。
另外组件也是常用的组件,一般用在电商类网站产品详情页,鼠标移到图片位置放大图片,具体怎实现就不详说 如图:
说说Vue3 Composition Api
vue2中,我们会在methods,computed,watch,data中等等定义属性和方法,共同处理页面逻辑,我们称这种方式为Options API。相信用过的都知道,方便是挺方便,不过缺点也很明显,当项目越来越大,很可能一个methods下就有二三十个方法, 还得确切地知道方法中可以访问哪些属性以及this关键字的行为,维护起来就十分麻烦了。Composition API(组合API)就是解决这个问题而生的。另外vue3是向下兼容的,也就是说允许我们用vue2的写法在vue3写,但既然是个新东西,我们就要尝试体验,看看有什么改变给自己带来什么便利。
Composition API它类似 react hooks写法。这次我直接用 setup 的语法糖编写这个组件。
setup 语法糖
什么是setup 语法糖?想要使用 setup
模式只要在 script
标签上面加个 setup
属性就可以了。这个模式下不需要 return
和 export
就可以在模板中使用。
<script setup>
</script>
编写组件
先上组件完整代码
<script setup>
<template>
<div class="imgBox" ref="imgbox">
<slot></slot>
<div class="mask"
v-show="state.isShow"
:style="{
left:state.maskX + 'px',
top :state.maskY +'px'}">
</div>
</div>
<transition name="fade">
<div class="zoomBox"
v-show="state.isShow"
:style="{left :state.boxX +'px',top: state.boxY +'px',}">
<img :src="state.imgSrc"
:style="{
width:state.zoomImgWidth + 'px',
height: state.zoomImgHeight + 'px',
marginLeft :-state.bImgX + 'px',
marginTop : -state.bImgY + 'px'
}">
</div>
</transition>
</template>
<script setup>
import {defineProps, onMounted, onUnmounted, reactive, ref} from 'vue'
const maskWidth = 50,
maskHeight = 50
const imgbox = ref(null)
const props = defineProps({mode: String})
const state = reactive({
zoomImgWidth: 0,
zoomImgHeight: 0,
isShow: false,
boxX: 0,
boxY: 0,
maskX: 0,
maskY: 0,
bImgX: 0,
bImgY: 0,
imgSrc: ''
})
const zommIn = (ev) => {
const img = ev.currentTarget.getElementsByTagName('img')[0]
const {offsetWidth, offsetTop, offsetHeight} = imgbox.value
state.zoomImgWidth = offsetWidth * 3
state.zoomImgHeight = offsetHeight * 3
state.imgSrc = img.src
state.boxX = offsetWidth
state.boxY = offsetTop
state.isShow = true
}
const zoomMove = (ev) => {
const {clientX, clientY} = ev
const {offsetTop, offsetLeft} = ev.currentTarget
const {offsetWidth, offsetHeight} = imgbox.value
let mx = clientX - offsetLeft - (maskWidth / 2),
my = clientY - offsetTop - (maskHeight / 2)
mx = mx < 0 ? 0 : mx
my = my < 0 ? 0 : my
if (mx > offsetWidth - maskWidth / 2) {
mx = offsetWidth - maskWidth / 2
}
if (my > offsetHeight - maskHeight / 2) {
my = offsetHeight - maskHeight / 2
}
state.maskX = mx
state.maskY = my
state.bImgX = mx * (state.zoomImgWidth - offsetWidth) / (offsetWidth - maskWidth)
state.bImgY = my * (state.zoomImgWidth - offsetWidth) / (offsetWidth - maskWidth)
}
const zommOut = () => {
state.isShow = false
}
//绑定方法
onMounted(() => {
imgbox.value.addEventListener('mouseover', zommIn)
imgbox.value.addEventListener('mousemove', zoomMove)
imgbox.value.addEventListener('mouseout', zommOut)
})
onUnmounted(() => {
imgbox.value.removeEventListener('mouseover', ()=>{})
imgbox.value.removeEventListener('mousemove', ()=>{})
imgbox.value.removeEventListener('mouseout', ()=>{})
})
</script>
<style scoped>
.mask {
width: 50px;
height: 50px;
background-color: #fff;
opacity: .6;
cursor: crosshair;
position: absolute;
}
.imgBox {
position: relative;
overflow: hidden;
width: 200px;
height: 200px;
}
.zoomBox {
width: 200px;
height: 200px;
position: absolute;
border: 1px solid #eee;
background: #fff;
overflow: hidden;
}
.fade-enter-active, .fade-leave-active {
transition: opacity .5s
}
.fade-enter, .fade-leave-active {
opacity: 0
}
</style>
</script>
看到这种模式编程,不就是函数式编程吗!!接下来我就说说我是怎样使用这些API
1.依赖注入defineProps
, onMounted
, onUnmounted
, reactive
, ref
这些函数都是在这个组件用到的,看到函数的命名,是不是有点似曾相识的感觉呢,没错,除了reactive
比较陌生 其他就是你想的!
import {defineProps, onMounted, onUnmounted, reactive, ref} from 'vue'
2.defineProps
这个相当于props,组件传入的参数,然后通过保存props变量读取
const props = defineProps({mode: String})
reactive
这个方式类似于我们设置的data选项, 但绑定到模板上就需要带上state
这个函数目的是为了观察引用数据类型。想直接绑定变量到模板上可以通过toRefs
解构模板变量。
const state = reactive({
zoomImgWidth: 0,
zoomImgHeight: 0,
isShow: false,
boxX: 0,
boxY: 0,
maskX: 0,
maskY: 0,
bImgX: 0,
bImgY: 0,
imgSrc: ''
})
4.ref
这个也是类似设置data的选项, 但它一般是观察原始数据类型,另外还可以类似vue2通过绑定ref获取dom信息,这次的ref我也是用在获取dom信息,当然你也可以用来设置可观察数据
// 绑定ref
<div class="imgBox" ref="imgbox"></div>
//获取dom信息
const imgbox = ref(null)
5.onMounted
和 onUnmounted
这两个方法类似 mounted 的钩子函数,
相信熟悉vue的对钩子应该也不会陌生,这里我直接做事件监听
onMounted(() => {
imgbox.value.addEventListener('mouseover', zommIn)
imgbox.value.addEventListener('mousemove', zoomMove)
imgbox.value.addEventListener('mouseout', zommOut)
})
onUnmounted(() => {
imgbox.value.removeEventListener('mouseover', ()=>{})
imgbox.value.removeEventListener('mousemove', ()=>{})
imgbox.value.removeEventListener('mouseout', ()=>{})
})
组件调用
组件调用也简化了,直接import
进来,不用在components
注册组件。
import zoom from './components/zoom.vue'
<zoom>
<img alt="Vue logo" src="./assets/logo.png"/>
</zoom>
最后
vue3还提供很多api 在这个组件没用到 ,如emit、watch、computed、provide、inject、还有各生命周期钩子函数,这些都是在vue2 熟悉的东西 ,但在vue3允许你用其他形式来编写,喜欢这种编写方式,就会感觉写得很爽。 对于用react的同学也是可以无缝接入,很快就可以上手,反之在vue3用过Composition Api的同学去学习react hooks 也是很容易上手。