大家好,我是前端小张同学,最近忙于工作都没有时间更新文章了,今天跟大家分享一个我在工作写的bug吧,其实没有什么技术含量,但是是一个值得深思的问题。
1:需求描述
需求是,点击一个 盒子DOM,然后需要打开一个弹窗,并且弹窗 弹窗可以是任意的,但弹窗里有一个按钮,点击按钮可以去关闭这个弹层,大概的需求就是这样子。为了更好的展示,下方是示例
2:神奇的问题
1:点击确认 无法关闭弹窗?
2:点击确认后 关闭了,但第二次无法重新唤起。
3: 代码
这个项目使用vue3写的一个需求,做一个银行卡选择列表,是以 webView 的形式嵌入在App中,当然在这里的代码是demo示例。
index.vue
此文件 内容主要是放入了一个弹层组件以及,可以去点击 class 为 wapper
dom 进行打开 actionSheet 弹层。
js
<template>
<div class="wapper" @click="clickHandle">
<div class="item">
<actionSheet v-model="showModal" />
</div>
</div>
</template>
<script setup lang="ts">
import { ref, watch } from 'vue';
import actionSheet from './components/actionSheet.vue';
const showModal = ref(false)
const clickHandle = ()=> {
console.log('chufa1');
showModal.value = true
}
watch(()=> showModal.value, (newVal)=> {
console.log('最新值更新', newVal);
})
</script>
<style scoped lang="less">
.wapper{
height: 100vh;
.item{
width: 100%;
height: 80px;
background-color: #ccc;
}
}
</style>
到这里,你可以先简单知道它DOM树结构是什么,我们继续向下看。
actionSheet.vue
此文件 里面放了一个二次封装的弹层组件,其实到这里你可以看出来,真正的弹层组件应该是 drawer
, 该组件在监听 index.vue
v-mode 传入的 modelValue
当 modelValue 发生变化时,在组件内部维护一个状态给到 drawer
组件,当 vmOpen
为 true 时 则可以展示 弹层。
js
<template>
<drawer
v-model:visible="vmOpen"
class="custom-class"
btn-text="确认"
@confrim="closeModal"
>
<p>Some contents...</p>
<p>Some contents...</p>
</drawer>
</template>
<script lang="ts" setup>
import { defineEmits, defineProps, ref, watch } from 'vue';
import drawer from './drawer.vue';
const emit = defineEmits(['update:modelValue'])
const props = defineProps({
modelValue: {
type: Boolean,
}
})
const vmOpen = ref<boolean>(false);
const closeModal = ()=> {
vmOpen.value = false
emit('update:modelValue', false)
}
watch(()=> props.modelValue, (newVal)=> {
vmOpen.value = newVal
})
</script>
darwer.vue
js
<template>
<div v-show="visible" class="drawer-wapper">
<div class="tanceng">
<slot name="default" />
<button v-if="btnText" class="close" @click="emit('confrim')">{{ btnText }}</button>
</div>
</div>
</template>
<script lang="ts" setup>
import { defineEmits, defineProps, onMounted } from 'vue';
const emit = defineEmits(['confrim'])
const props = defineProps({
visible: {
type: Boolean,
default: false
},
btnText: {
type: String,
default: ''
}
})
onMounted(()=> {
console.log('props', props.visible);
})
</script>
<style scoped lang="less">
.drawer-wapper{
width: 100%;
height: 100%;
position: fixed;
top: 0;
left: 0;
bottom: 0;
background-color: rgb(0 0 0 / 30%);
.tanceng{
width: 100%;
height: 200px;
position: absolute;
bottom: 0;
animation: trans linear .3s ;
background-color: #fff;
.close{
width: 100%;
}
}
@keyframes trans {
0%{
transform: translateY(100%);
}
100%{
transform: translateY(0%);
}
}
}
</style>
ok,到这里 ,捋一下逻辑,大概是这个样子 。
看完上方逻辑图,你应该了解 index.vue 绑定了一个 showModal
状态 ,点击 dom节点更改 showModal 值为 true, 当 showModal 值发生变化时,actionSheet
组件 监听变化,保存一个组件内部的状态,当组件内部状态vmOpen
发生变化时 为true 时,弹层就展示了,当点击弹层组件里的确认发射 @confirm
事件去关闭弹层。这样整体下来思路没什么问题,完美形成了一个闭环,可奇怪的是,这个 弹层组件关闭不掉,请看下方。
好的,我的掘友们,到这里你可以综合上方代码去发现一下,为什么不生效,让时间停在这里,想一下哪里有问题。
解答
其实大家,如果没有想明白为什么,你可以 拿着我的代码去跑一个demo 你就明白了,其实最根本的原理就是 drawer 组件内部 发射事件之前 没有做 冒泡处理,但我们 actionSheet
closeModal函数中 也没有进行点击事件的 阻止冒泡,stopProgation(),导致它又触发了 外层的 index.vue
clickHandle 事件 所以无法关闭,虽然这个bug很普通,但涉及的知识点也算是还可以。
知识点
1:事件冒泡,事件捕获的过程
2:弹层组件的实现以及原理
3:v-model的原理及本质
4:vue3中 如何更新 v-mode的值
拓展
在上诉代码中,有没有发现我们 watch 监听值非常的麻烦,还需要组件内部在定义一个变量,再保存。
在这里就要跟大家讲解vueUse中 新的 Hooks
了 叫 useVModdel
它的作用是什么?
它可以帮助我们,通过赋值的形式进行触发 update:xxx 更新v-model的值。
Usage 官方示例
js
import { useVModel } from '@vueuse/core'
export default {
setup(props, { emit }) {
const data = useVModel(props, 'data', emit)
console.log(data.value) // props.data
data.value = 'foo' // emit('update:data', 'foo')
},
}
我们在setup语法糖中的用法,可以这样
js
<template>
<drawer
v-model:visible="vmOpen"
class="custom-class"
btn-text="确认"
@confrim="closeModal"
>
<p>Some contents...</p>
<p>Some contents...</p>
</drawer>
</template>
<script lang="ts" setup>
import { useVModel } from '@vueuse/core';
import { defineEmits, defineProps, watch } from 'vue';
import drawer from './drawer.vue';
const emit = defineEmits(['update:modelValue'])
const props = defineProps({
modelValue: {
type: Boolean,
}
})
const vmOpen = useVModel(props, 'modelValue', emit) // 将你的 props 和 emit 以及 需要转换的key 传入
// const vmOpen = ref<boolean>(false);
const closeModal = (e:any)=> {
e.stopPropagation();
vmOpen.value = false // 同等与 emit('update:modelValue' , false)
emit('update:modelValue', false)
}
watch(()=> props.modelValue, (newVal)=> {
vmOpen.value = newVal
})
</script>
原理
useVModel 使用了 computed 进行中间代理,当你设置值的时候,他会自动帮你派发 emit
事件,对于开发是一个较好的选择。
本文到这里就结束啦,谢谢,我是前端小张同学