[封装自己的ui组件库] 封装cascader级联选择器时遇到的问题

474 阅读2分钟

效果

QQ录屏20221122003430.gif

问题

在开发级联选择器的时候出现了一个问题

1、首先在选择框上,我是通过递归数据options,给其添加上后面级联时所需要的数据

// 初始化处理(设置层级id, 设置上一级指向)
 initHandler(parent, obj, index) {
     // 层级
     obj.uid = index;
     // 指向上一级的指针
     obj.pre = parent;
     // 默认没有选中
     obj.select = false;
     // 有子元素则继续初始化处理
     if (obj.children) {
         obj.children.forEach(child => {
             this.initHandler(obj, child, index + 1)
         })
     }
   return obj;
},

  watch: {
        options: {
            handler(newV) {
                if (!newV.length) return;

                this.frist.child = newV.map(child => {
                    // 初始化数据
                    return this.initHandler(null, child, 1)
                });
            }, immediate: true, deep: true
        },
}

后面当我点击对应的子项时,子项会通过provide/inject给根组件触发一个点击事件,在根组件中对数据进行选中方面的修改。但是修改时却发现后代项无法监听到数据的更改(涉及到多个组件的嵌套)

调试后发现是因为根组件是将数据列表发送给组件1, 而组件1将数据列表的每一项中的数据发送给组件2

这个过程中根组件的数据列表是响应式的, 在传递给子组件后,因为本身是响应式数组的元素,其不是响应式,所以组件2接受到的数据并不是响应式。

解决 通过Object.assign()混入,将非响应数据混入到一个响应的空对象,再将响应式对象发送到组件2

 deepList: {
            get() {
                return this.data.child.map(child => {
                    // 问题
                    // 这里如果传递不加处理的child到deep组件时会出现deep无法监听到父组件更新的清况
                    // 因为这里的child是响应式数组的元素,本身不是响应式的,所以deep无法监听child内部变化

                    // 解决 混入
                    // 通过assign合并对象, 将响应式的{}与非响应式的child合并得到一个响应式的对象
                    // deep就可以监听到child内部的变化
                    return Object.assign({}, child)
                })
            }
        }

2、还有再点击展开弹窗时应该在点击其他位置时关闭弹窗

这里使用了自定义指令

 <!-- 弹出 -->
  <div class="Eject" v-if="isPop" v-clickOutside="handlerOutside">
      ......
  </div>
        
        
   methods:{
       ...
       // 点击非当前元素触发
        handlerOutside() {
            this.isPop = false
        }
       ...
   }
        
        
指令////
export default {
    name: 'clickOutside',
    directive: {
        bind(el, binding) {
            // 点击非当前组件触发
            function clickOutSideHandler(e) {

                // 判断当前元素是否是在外部触发点击

                /* 
                    el.contains(e.target) 检测某元素是否包含在另一元素
                */
                if (el.contains(e.target)) {// 触发元素在某元素内
                    return false
                }

                // 触发元素在某元素内
                if (binding.value && typeof binding.value == 'function') binding.value(e)
            }

            // 在el上绑定方法
            el.outSide = clickOutSideHandler;

            // 注册全文档页面的点击事件
            document.addEventListener('click', clickOutSideHandler)
        },

        unbind(el) {
            // 指令绑定元素卸载时卸载事件, 同时清空el上的属性
            document.removeEventListener('click', el.outSide)

            el.outSide = null
        }
    }
}