vue3.0出来已经有段时间的了,也与必要开始研究它了!
先看下我们要实现的效果
很常见的展开显示菜单项的内容,在vue3.0里面怎么开发,这里样式我们用的是bootstrap的默认样式
思路一:
<DropDown :title="'退出'" :list="menuLists" />
思路二:
<drop-down :title="'退出'">
<drop-dowm-item>新建文章</drop-down-item>
<drop-dowm-item>编辑文章</drop-down-item>
<drop-dowm-item>个人信息</drop-down-item>
</drop-down>
两种思路都行,相比较而言,第二种思路比较清晰,使用的时候知道具体的层次,也是elementUI组件开发的模式.
现在就第二种组件开发思路进行分析
DropDown.ts
<template>
<div class="dropdown" ref="dropDownRef">
<a
@click.prevent="toggleOpen"
class="btn btn-secondary dropdown-toggle"
href="#" rel="external nofollow"
>
{{ title }}
</a>
<div class="dropdown-menu" :style="{ display: 'block' }" v-show="isOpen">
<slot></slot>
</div>
</div>
</template>
js部分
<script lang="ts">
import { defineComponent, ref, onMounted, onUnmounted, watch } from "vue";
import useClickOutside from "../hooks/useClickOutside";
export default defineComponent({
name: "DropDown",
props: {
title: {
type: String,
required: true,
},
},
setup(context) {
const isOpen = ref(false);
//vue3.0获取dom对象的引用
const dropDownRef = ref<null | HTMLElement>(null);
const toggleOpen = () => {
isOpen.value = !isOpen.value;
};
const handleClick = (e: MouseEvent) => {
console.log(e.target, "e");
if (dropDownRef.value) {
console.log(dropDownRef.value);
if (
//contains判断节点是否包含节点
!dropDownRef.value.contains(e.target as HTMLElement) &&
isOpen.value
) {
isOpen.value = false;
}
}
};
onMounted(() => {
//注册全局的点击事件
document.addEventListener("click", handleClick);
});
onUnmounted(() => {
//解绑
document.removeEventListener("click", handleClick);
});
return {
isOpen,
toggleOpen,
dropDownRef,
};
},
});
</script>
DropDownItem.ts
<template>
<li class="dropdowm-option" :class="{ 'is-disabled': disabled }">
<slot></slot>
</li>
</template>
<style scoped>
/* 此处是插槽需要穿透 */
.dropdowm-option.is-disabled >>> * {
color: #6c757d;
pointer-events: none;
background-color: transparent;
}
</style>
<script lang="ts">
import { defineComponent } from "vue";
export default defineComponent({
props: {
disabled: {
type: Boolean,
default: false,
},
},
setup() {
return {};
},
});
</script>
到这里这个组件就完成了。但是…我们可以看到点击整个document隐藏这个事件与整个组件的关联不大,因此我们可以抽取成一个hooks
useClickOutside.ts
import { ref, onMounted, onUnmounted,Ref } from 'vue'
const useClickOutside = (elementRef:Ref<null | HTMLElement>) => {
const isClickOutside = ref(false)
const handler = (e: MouseEvent) => {
console.log(elementRef.value);
if (elementRef.value) {
if (elementRef.value.contains(e.target as HTMLElement)) {
isClickOutside.value = false
} else {
isClickOutside.value = true
}
}
}
onMounted(() => {
document.addEventListener("click", handler);
});
onUnmounted(() => {
document.removeEventListener("click", handler);
});
return isClickOutside
}
export default useClickOutside
然后再改写我们的DropDown.ts组件
//删掉之前已有的事件逻辑
<script lang="ts">
...
const isClickOutside = useClickOutside(dropDownRef);
/* console.log(isClickOutside.value, "isClickOutside"); */
//引入监听方法,数据变化时我们改变isOpen的值为false
watch(isClickOutside, (newValue) => {
if (isOpen.value && isClickOutside.value) {
isOpen.value = false;
}
});
...
</script>
实现了同样的效果,整个组件的代码也精简了不少!