vue3 实现 select 下拉选项

呃哼~ 第一次发帖. 写不好请见谅

本人学生 , 平时在外面没事接点小项目小赚一笔补贴生活费. 之前一直都是使用Vue2.x的版本做项目, 暑假刚刚学习了Vue3想着新项目就直接用Vue3上手.

效果展示

好了, 话不多说先给大佬们看看效果样式:


bs470-ngit5.gif

组件难点

因为下拉框可能会在某些情况下被挡住, 所以这里的下拉框被挂载到了body标签上, 并且下拉框中的选项往往是以插槽的形式编写, 这里就会困扰到很多小白, 搞不明白怎么样才能在 下拉框触发下拉按钮 之间关联响应式事件与数据.

组件的使用


    



参数说明

tk-selectselect下选项父标签, 必须含有插槽 #selectDropDown 才能正常使用

Attribute Description Accepted Values Default
selected 默认选中的值,如果不填或为空则默认选中插槽中的第一个 tk-select-item 中的值 - -

tk-select-item 为**select
**下选项子标签(选项标签), tk-select-item 内可以继续写入其他 HTML 内容, 每项的具体值由props value 决定

Attribute Description Accepted Values Default
value 词选项默认返回的数据 (必须设置) - -

v-modal

可以使用 v-modal 实时获取到 下拉选项 选取到的值

注意:这里的 v-modal并没有做成双向绑定, 这里只用于获取到 select 中选中的值, 只能用于获取, 主动修改其值并无效果, 并且不支持 v-model 修饰符


    ...



实现思路

首先看看目录结构

src
 |
 |
 |-- components
 |      |
 |      |-- select
 |            |
 |            |-- select.vue
 |            |-- select-item.vue
 |            |-- selectBus.js
 |
 |
 |-- utils
 |    |-- token.js

两个 .vue 文件用来的干嘛的没什么好说的, selectBus.js 解决 Vue3 中无法安装 eventBus 的问题, token.js 用于给每组 select 与 select-item 相互绑定.

首先我们看看 selectBus.js 里面的内容

我们先看看 vue3 官网怎么说的 进入官网. 说人话的意思就是不可以像 vue2 那样愉快的安装Bus, 需要自己实现事件接口或者使用第三方插件. 这里官网也给出了具体实现方案.

// selectBus.js
import emitter from 'tiny-emitter/instance'

export default {
    $on: (...args) => emitter.on(...args),
    $once: (...args) => emitter.once(...args),
    $off: (...args) => emitter.off(...args),
    $emit: (...args) => emitter.emit(...args)
}

select.vue 文件是我们的父组件

vue3 新增 标签, 可以将标签内的元素挂载到任意位置, 查看官方文档

// teleport 用法
// 将

挂载到body上

标题

select 主要有触发下拉按钮tk-select-button和下拉列表tk-select-dropdown组成, 下拉框中的选项未来将由插槽插入.



首先解决下拉列表打开&关闭和定位的问题

import { ref, onDeactivated } from 'vue';
export default {
    // 获取按钮
    const select_button = ref(null);
    // 获取下拉框
    const select_dropdown = ref(null);
    
    // 下拉框位置参数
    const dropdownPosition = ref({x:0,y:0,w:0})

    // 下拉框位置
    const dropdownStyle = computed(()=>{
        return {
            left: `${dropdownPosition.value.x}px`,
            top:  `${dropdownPosition.value.y}px`,
            width: `${dropdownPosition.value.w}px`
        }
    })
    
    // 计算下拉框位置
    function calculateLocation(){
        var select_button_dom = select_button.value.getBoundingClientRect()
        dropdownPosition.value.w = select_button_dom.width
        dropdownPosition.value.x = select_button_dom.left
        dropdownPosition.value.y = select_button_dom.top + select_button_dom.height + 5
    }
    
    // 每次下拉框打开时重新计算位置
    watch(selectOpen,(val)=>{
        if(val)
            // 计算位置
            calculateLocation();
    })
    
    // ---------------------------------增加一点修饰---------------------------------------
    // 点击非按钮或下拉框区域也会收起下拉框
    window.addEventListener('click',(event)=>{
        if(!select_button.value.contains(event.target) && !select_dropdown.value.contains(event.target) ){
            selectOpen.value = false
        }
    })
    
    // 当页面滚动或改变大小时重新计算位置
    window.addEventListener('resize',()=>{
        // 计算面板位置
        calculateLocation();
    })
    window.addEventListener('scroll',()=>{
        // 计算面板位置
        calculateLocation();
    })
    
    // 当组件卸载时释放这些监听
    onDeactivated(() => {
        window.removeEventListener('resize')
        window.removeEventListener('scroll')
        window.removeEventListener('click')
    })
    
    return {
        select_button,
        select_dropdown,
        dropdownPosition,
        dropdownStyle,
        calculateLocation
    }
}

让我们继续看看select-item.vue , 这是我们的子组件





select.vue 中接收事件

setup(){
    // 选中内容
    const selctValue = ref('');
    ...
    onMounted(()=>{
        Bus.$on('chooseSelectItem',(res)=>{
            // 修改显示值
            selctValue.value = res.value
            // 关闭下拉框
            selectOpen.value = false
        })
    })
    ...
}

到这里下拉选项框基本就完成了. 我们像页面添加第一个下拉选项时非常完美,但是如果页面上有两个select存在时问题来了. 我们发现当控制其中一个选项被选中是, 另外一个select显示的值也随之改变. 我们需要将一组 select & select-item 进行绑定,让Bus在接受时知道事件来自于哪个里面的 select-item.

vue2中我们通常获取实例的parent然后一层一层寻找父类select, 但是在 vue3 setup中并不能获取到正确的parent, 所以我想到了可以在 select 创建时派发一个 token 在讲此令牌传给所有子类, 好了理论存在, 开始实践.

provide & inject

在vue中使用provide可以向子类、孙类等等后代传输数据, 后代使用inject接收数据.查看官网

components_provide.png

派发token令牌

这里可以模仿Java中的UUID

// token.js
function code() {
    return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);
}

export function tokenFun() {
    return (code() + code() + "-" + code() + "-" + code() + "-" + code() + "-" + code() + code() + code());
}

select 创建时生成 token 并派发给后代

// select.vue
import {tokenFun} from '@/utils/token'
import {provide, getCurrentInstance} from 'vue';

...

setup(){

    ...
    
    // 获取实例
    const page = getCurrentInstance()

    var token = 'select-' + tokenFun();
    // 缓存token
    page.token = token
    // 给子元素派发token
    provide('token',token)
  
  return {
      token
  }
}

这样我们在子类接收后每次使用bus发送数据时带上token

// select-item.vue
import {ref, getCurrentInstance, inject} from 'vue';

...

setup(){

    ...
    
    // 获取实例
    const page = getCurrentInstance();
    
    // 接收token
    const token = inject('token');
    // 缓存token
    page.token = token
    
    // 选择下拉
    function chooseSelectItem(){
        // 在使用Bus发送数据时带上token
        Bus.$emit('chooseSelectItem',{token: token,value: props.value});
    }
}

select.vue 监听Bus后先验证token

onMounted(()=>{
    Bus.$on('chooseSelectItem',(res)=>{
        // 判断发送数据的子孙携带的token是否和实例一样
        if(res.token === page.token){
            // 修改显示值
            selctValue.value = res.value
            // 关闭下拉框
            selectOpen.value = false
        }
    })
}) 

大功告成, 这样我们就做好了一个select下拉选项, 下拉部分挂于body标签

全部代码

select.vue






select-item.vue






token.js

function code() {
    return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);
}

export function tokenFun() {
    return (code() + code() + "-" + code() + "-" + code() + "-" + code() + "-" + code() + code() + code());
}

selectBus.js

import emitter from 'tiny-emitter/instance'

export default {
    $on: (...args) => emitter.on(...args),
    $once: (...args) => emitter.once(...args),
    $off: (...args) => emitter.off(...args),
    $emit: (...args) => emitter.emit(...args)
}

GitHub源码地址

github.com/18651440358/vue3-select

第一次写帖子几分激动几分不知所措, 请各位大佬指点错误或可以优化的地方, 欢迎大家讨论.

你可能感兴趣的:(vue3 实现 select 下拉选项)