二十一.vue3之el-select封装集成

前言

最近写表单的时候被测试提了好几个bug,最后排查发现主要是el-select引起的。

  • 第一种情况是远程搜索,页面初始化的时候默认获取50条下拉,然后远程搜索了一个值,保存后再进详情,由于远程搜索的该值不在默认获取的50条里面,所以导致显示的code
  • 第二个问题类似,选择了一个值后,然后从字典里面把这个值删了,导致回显的code
  • 第三个问题 和后台约定存储时下拉的namecode都需要存储,由于el-select默认只能绑定一个code,所以导致每次都需要change然后赋值,比较麻烦;
  • 第四个问题如果页面有好多个select,初始化的时候就问请求n个接口,导致页面变慢,性能上不友好。

所以这里就对el-select进行一次二次封装,彻底解决掉这些问题,提升开发效率,这里记录下解决过程和思路。

回显问题

关于这个回显问题,也进行搜索了很久,但是基本上都不符合预期,要么就是初始化拿数据的时候把当前数据push到下拉的options里面去,要么就是通过cachedOptions进行push,或者直接说让用element-plus中的虚拟select(element-ui:那我走?)。

鉴于网上搜索的都不行,就只能自己想了,这个时候发现下拉选项都是通过options配置的,如果我给他加一个默认的option,是不是就能解决值无法回显的问题了?对现有代码进行改造。

js 复制代码
    <el-select v-model="form.code" placeholder="请选择">
        <el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value" />
        <el-option :label="form.name" :value="form.code" v-if="(form.name||form.code)&&(options.length === 0 || !options.find(item => item.value === form.code)"></el-option>
    </el-select>

当然这个默认的option也不是时候都显示的,例如当默认下拉选项中包含这个code时候,就不需要显示,但是如果每个select这样写代码复用性太差了,所以这里需要对select进行一个封装。

封装select

封装之前我们需要考虑以下几个问题:

  • options下拉选项是通过props传递进来的,需要考虑options的值不为labelvalue的情况。
  • 需要兼容同时绑定labelvalue值,不需要再change赋值;
  • change有时候想要当前选中的下拉项,而不仅仅是value值;

基于上面的考虑,我们开始对select进行封装:

js 复制代码
# base-select.vue

<template>
  <el-select v-model="selectValue" :multiple="multiple" placeholder="请选择" :value-key="fieldNames.value" @change="changeSelect" v-bind="$attrs">
  <el-option v-for="item in options" :key="item[fieldNames.label]" :label="item[fieldNames.label]" :value="valueCompute(item)" />
        <el-option v-for="item in defaultOption" :key="item[fieldNames.label]" :label="item[fieldNames.label]" :value="valueCompute(item)" />
  </el-select>
</template>

<script setup>
  const props = defineProps({
    value: {
      type: [String, Array]
    },
    label: {
      type: [String, Array]
    },
    options: {
      type: Array,
      default: () => []
    },
    // 是否change的时候返回当前下拉项值
    labelInValue: {
      type: Boolean,
      default: true
    },
    // 配置option中的想绑定的label和value字段
    fieldNames: {
      type: Object,
      default: () => ({ label: 'label', value: 'value' })
    },
    //value是否是当前item
    valueObject: {
      type: Boolean,
      default: false
    },
    //是否多选
    multiple: {
      type: Boolean,
      default: false
    }
  });
  
  const emits = defineEmits(['update:value', 'update:label', 'change']);
  
  //这里使用vueuse中的useVModel,也可以不使用,自己写也行。
  const selectValue = useVModel(props, 'value', emits, { passive: true, defaultValue: props.value });
  
 //value绑定的值,看是否需要绑定整个字段
  const valueCompute = computed(() => {
    return (val) => {
      return props.valueObject ? val : val[props.fieldNames.value];
    };
  });
  
  //默认下拉框需要出现的逻辑,之所以这里改成是一个数组,是因为他可能多选。
  const defaultOption = computed(() => {
       const { multiple, valueObject, label, value } = props;
        if(!value) return [];
        if (multiple) {
            if (valueObject) {
               return value.filter((item) => !hasItem(item));
            }
            const arr = [];
            value.forEach((item, index) => {
               if (!hasItem(item)) {
                  arr.push(joinArr(label[index], item));
               }
            });
            return arr;
       } else if ((label || value) && !hasItem(value)) {
          return [joinArr(label, value)];
       }
       return [];
  });
  
  const hasItem = (value) => {
       const val = props.valueObject ? value[props.fieldNames.value] : value;
       return props.options && props.options.find((item) => item[props.fieldNames.value] === val);
  };
  
  const joinArr = (label,value) => {
      const { fieldNames } = props;
      return {
          [fieldNames.label]: label,
          [fieldNames.value]: value
      };
  };
  
  const changeSelect = (val) => {
    const options = props.options;
    if (props.valueObject) {
      emits('change', val);
      return;
    }
    const currentSelect = options.find(item => item[props.fieldNames.value] === val);
    emits('update:label', currentSelect?.[props.fieldNames.label]);
    emits('change', currentSelect || {});
  };
</script>

这里核心逻辑点 的是这个默认下拉是否出现的场景,由于我们下拉框分为单选、多选value对象,多选普通value等情况。所以这里要进行一个区分,至于为什么返回的是一个数组,是因为可能是多选,如果多选里面多个值无法正常回显,这里就需要添加多个。

至于同时绑定labelvalue,其实就是change的时候updatelabel绑定的值即可。

change想获取当前下拉项,其实和上面绑定label一样,我们重写这个change事件,1.更新label 2.将当前下拉项次emit出去。

至于el-select一些其他属性例如clearable这些,或者el-select的一些其他事件,这里我们使用了v-bind,他会自动将没有被props接受的参数向下传递,所以这里就不需要写其他的属性。在vue3v-bindv-on进行了合并,只需要写v-bind即可。

使用:

js 复制代码
<template>
//如果是vue2,可以使用label.sync
 <base-select v-model:label="form.name" v-model:value="form.code" :options="options" :fieldNames="{label:'fieldName',value:'fieldCode'}" labelInValue/>
</template>

select扩展

其实上面select还可以进一步扩展,这个之所以独立出来,是因人而异,有的人喜欢基础版觉得简单可进一步定制,有人喜欢功能强大pro版,开箱即用,所以这里单独独立出来。例如对select的下拉,远程搜索进行扩展,扩展成 apiSelect等。

我们可以给select再加几个参数:apiparamsremotequeryField

  • api :请求optionsurl 地址;
  • params :请求url的其他参数;
  • remote:是否开启远程搜索;
  • queryField :远程搜索的关键字参数,默认为keyword
js 复制代码
# base-select.vue
....其他代码
<script setup>
....其他代码
 
 onMounted(){
    if(props.api){
        getOptions()
    }
 }
 
 //远程搜索也调用这个方法即可
 const getOptions=(keyword)=>{
    const {api,params,queryField}=props
    const requestParams={
      [queryField]:keyword,
      ...params,
    }
   
    request(api,{...requestParams}).then(res=>{
    
    }) 
 }

</script>

使用:

js 复制代码
<template>
//如果是vue2,可以使用label.sync
 <base-select v-model:label="form.name" v-model:value="form.code" :fieldNames="{label:'fieldName',value:'fieldCode'}" :api="xxxx" />
</template>

性能优化

由于页面可能有多个select,初始化的时候都会获取接口,导致页面初始化整体性能不好,所以这里我们可以进行一次优化,只有在focus的时候,如果这个时候没有初始化,就去请求获取options的接口。

伪代码:

js 复制代码
<el-select @focus="focusSelect">
</el-select>


<script setup>
const isInit=ref(false)

const focusSelect=()=>{
   if(!isInit.value){
     getOptions()
   }
}
</scipt>

最后

经过这个select组件的封装,原本代码中大量的change事件都没有了,整体代码看起来清晰舒服了,希望该文章对你有帮助。

其他文章

相关推荐
小白小白从不日白2 分钟前
react 组件通讯
前端·react.js
Redstone Monstrosity19 分钟前
字节二面
前端·面试
东方翱翔26 分钟前
CSS的三种基本选择器
前端·css
Fan_web1 小时前
JavaScript高级——闭包应用-自定义js模块
开发语言·前端·javascript·css·html
yanglamei19621 小时前
基于GIKT深度知识追踪模型的习题推荐系统源代码+数据库+使用说明,后端采用flask,前端采用vue
前端·数据库·flask
千穹凌帝1 小时前
SpinalHDL之结构(二)
开发语言·前端·fpga开发
冯宝宝^1 小时前
基于mongodb+flask(Python)+vue的实验室器材管理系统
vue.js·python·flask
dot.Net安全矩阵1 小时前
.NET内网实战:通过命令行解密Web.config
前端·学习·安全·web安全·矩阵·.net
Hellc0071 小时前
MacOS升级ruby版本
前端·macos·ruby
前端西瓜哥1 小时前
贝塞尔曲线算法:求贝塞尔曲线和直线的交点
前端·算法