IwishIcould

场景

当我们使用 Select 选择器存放大量数据的时候。
会发现存在这么2个问题。
1.接口响应时间较长。(因为数据量较多,一次查询的所有)甚至有可能超时。
2.前端下拉框滑动卡顿。
这个时候们如何解决上面面临的问题呢?
有的小伙伴可能会说:
1.分页加载。确实是可以解决问题。
2.页面卡顿使用虚拟dom.超时喊后端自己优化代码
因为项目中使用的是 element-ui,没有虚拟加载。
不想 Ant Design Vue一样,有virtual属性,设置 true可以开启虚拟滚动。
这里就感觉到 Ant Design Vue处理的比element-ui好一些(希望各位大佬不要喷我)
所以我们只能选择异步加载。
当页面滑动到底部的时候,加载下一页的数据

下拉框滑动到底部触发事件,加载下一页的数据

我们首先需要做到的是:获取下拉框元素的DOM节点。
由于一个页面可能有多个dom节点。
我们需要使用 popper-class属性来设置	Select 下拉框的类名。
然后后通过dom.scrollHeight - dom.scrollTop <= dom.clientHeight
来判断是否触底了。下面我们就来实现了一下
// 子组件
<template>
  <div>
    <el-select popper-class="more-next-box" v-model="value" placeholder="请选择">
      <el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value">
      </el-option>
    </el-select>
  </div>
</template>

<script>
export default {
  data() {
    return {
      options: [{
        value: '001',
        label: '数据1'
      }, {
        value: '002',
        label: '数据2'
      }, {
        value: '003',
        label: '数据3'
      }, {
        value: '004',
        label: '数据4'
      }],
      value: ''
    }
  },
  mounted() {
    // 获取dom节点
    const domElementNode = document.querySelector('.more-next-box .el-select-dropdown__wrap')
    // 注册下拉滚动事件
    domElementNode.addEventListener('scroll',  ()=>{
      const isBottom = domElementNode.scrollHeight - domElementNode.scrollTop <= domElementNode.clientHeight
      if (isBottom) {
        console.log('是否到底了')
      }
    })
  },
}
</script>

现在我们已经成功判断是否下拉到底了。
接下来我们应该去封装一下这个组件的属性和事件
争取拥有官方文档中的所有属性和事件

如何让 el-select拥有官方的所有属性呢?

我们可以使用props属性,一个一个的写上去。
这个操作虽然可以但是官方文档多20+多个属性。
是不是太麻烦了呀?
我们可以使用 $attrs配合inheritAttrs: false 来解决这个问题
$attrs里面包含着父组件传递的所有数据(除style和class)。
当一个组件声明了prop时候,attrs里面包含除去prop里面的数据剩下的数据。
而inheritAttrs: false,表示将属性不设置在根元素上,而是设置在指定的元素上
//组件优化为
<template>
  <div>
    <el-select  v-bind="$attrs" v-model="value" >
      <el-option v-for="item in options" 
        :key="item.value" 
        :label="item.label" 
        :value="item.value">
      </el-option>
    </el-select>
  </div>
</template>

//表示不添加在组件的根元素上
inheritAttrs: false,
//页面使用
<SelectLoadMore  
  popper-class="more-next-box"  
  :clearable="true" 
  placeholder="请选择数据">
</SelectLoadMore>

如何让 el-select拥有官方的所有事件呢?

$listeners包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。
//组件再次优化为
<el-select v-on="$listeners"  v-bind="$attrs" v-model="value" >
  <el-option v-for="item in options" 
    :key="item.value" 
    :label="item.label" 
    :value="item.value">
  </el-option>
</el-select>
//页面使用
<div>
  <SelectLoadMore  popper-class="more-next-box"  
  :clearable="true"
  @visible-change="visibleChange" 
  placeholder="请选择数据"
  @change="changeHandler"
  ></SelectLoadMore>
</div>

el-option中key,label,value这些字段不一样怎么处理?

这个我们直接使用props来处理就好了。
这样我们就需要担心key键名不一致这个问题了
<div>
  <el-select v-on="$listeners"  v-bind="$attrs" v-model="value" >
    <el-option v-for="item in options" 
      :key="item[setKey]" 
      :label="item[setLabel]" 
      :value="item[setValue]">
    </el-option>
  </el-select>
</div>

props: {
  setKey: {
    type: String,
    default:'key'
  },
  setLabel: {
    type: String,
    default: 'label'
  },
  setValue: {
    type: String,
    default: 'value'
  }
},

下拉到底部加载下一页的数据

// 组件
created() {
  this.getList()
},
mounted() {
  // 获取dom节点
  const domElementNode = document.querySelector('.more-next-box .el-select-dropdown__wrap')
  // 注册下拉滚动事件
  domElementNode.addEventListener('scroll',  ()=>{
    const isBottom = domElementNode.scrollHeight - domElementNode.scrollTop <= domElementNode.clientHeight
    if (isBottom) {
      console.log('是否到底了')
      // 这里应该还有一个判断,总条数和当前列表中的数据做一个比较。
      // 这里我就不写了
      this.getList(this.pageSize += 1)
    }
  })
},
methods: {
  // 请求的数据
  getList(index = 1) {
    let arr = []
    setTimeout(() => {
      for (let i = 0; i < 10; i++){
        arr.push({
          value: (index-1) *10 + i,
          label: '数据'+ (index - 1) * 10 + i
        })
      }
      this.options=this.options.concat(arr)
    },2500)
  }
},

加载下一页的时候做一个加载提示

从上面这张图中,我们可以看见。
加载的时候没有任何的提示和效果。非常的不友好。
用户都不知道是在加载数据。我们现在优化一下
//子组件
<el-select v-on="$listeners"  v-bind="$attrs" v-model="value" >
  <el-option v-for="item in options" 
    :key="item[setKey]" 
    :label="item[setLabel]" 
    :value="item[setValue]">
  </el-option>
  <el-option v-show="loadingFlag"  label="正在努力加载中" value="xx01"></el-option>
</el-select>

data() {
  return {
    options: [],
    value: '',
    pageSize: 1,
    loadingFlag:false //控制加载显示的
  }
},

getList(index = 1) {
  this.loadingFlag=true  //开启加载中
  let arr = []
  setTimeout(() => {
    for (let i = 0; i < 10; i++){
      arr.push({
        value: (index-1) *10 + i+'',
        label: '数据'+ (index - 1) * 10 + i
      })
    }
    this.options = this.options.concat(arr)
    this.loadingFlag = false //加载结束
  },2500)
}

md发现了一个bug,用户选择‘正在努力加载中’中这一项怎么办?

有细心的小伙伴,可能会说:
<el-option  v-show="loadingFlag"  label="正在努力加载中" value="xx01"></el-option>
这样写真的没有问题吗?
万一用户选择了,这一项数据怎么办?
这,这,这,这,这恐怕就会100%出现bug了。
使用 disabled 来处理禁止选择这一项。
哈哈··,我简直是一个小天才(手动狗头)

使用 disabled 来处理禁止选择这一项

<el-select v-on="$listeners"  v-bind="$attrs" v-model="value" >
  <el-option v-for="item in options" 
    :key="item[setKey]" 
    :label="item[setLabel]" 
    :value="item[setValue]">
  </el-option>
  <el-option disabled v-show="loadingFlag"  label="正在努力加载中" value="xx01"></el-option>
</el-select>

继续优化加载动画

有的小伙伴说,这个虽然不能选择,也有加载提示。
但是是灰色的,不太友好。
你他娘的,就你屁事多。烦死了(悟空表情)。

<el-select v-on="$listeners"  v-bind="$attrs" v-model="value" >
  <el-option v-for="item in options" 
    :key="item[setKey]" 
    :label="item[setLabel]" 
    :value="item[setValue]">
  </el-option>
  <p v-show="loadingFlag">正在努力加载中</p> 
</el-select>

我们可以自己用一个标签来处理动画,这里我就不在做动画了。
其实如果你觉得 disabled的样式也可以修改的,

其实中的disabled也是可以修改样式的

<el-option disabled v-show="loadingFlag"  label="正在努力加载中" value="xx01"></el-option>

<style lang="scss" scoped>
.el-select-dropdown__item.is-disabled{
  color: red;
}
</style>

如何做数据回填

有些时候,我们除了需要选择值,还需要回填值。
我们可以使用 props来传递值,然后在created中赋值。
// 数据回填的值
writeData: {
  type: String,
  default: ''
}

created() {
  if (this.writeData) {
    this.value = this.writeData
  }
  this.getList()
},

全部代码

// 使用的页面
<template>
  <div>
    <SelectLoadMore  popper-class="more-next-box"  
    :clearable="true"
    :writeData="writeData"
    @visible-change="visibleChange" 
    placeholder="请选择数据"
    @change="changeHandler"
    ></SelectLoadMore>
  </div>
</template>

<script>
import SelectLoadMore from "@/components/SelectLoadMore.vue"
export default {
  components: {
    SelectLoadMore
  },
  data() {
    return {
      writeData: 108
    }
  },
  methods: {
    changeHandler() {
      console.log('值发生了改变')
    },
    visibleChange(flag){
      console.log('flag', flag)
    }
  },
}
</script>
组件
<template>
  <div>
    {{ value }}
    <el-select v-on="$listeners"  v-bind="$attrs" v-model="value" >
      <el-option v-for="item in options" 
        :key="item[setKey]" 
        :label="item[setLabel]" 
        :value="item[setValue]">
      </el-option>
      <!-- <p v-show="loadingFlag">正在努力加载中</p> -->
      <el-option disabled v-show="loadingFlag"  label="正在努力加载中" value="xx01"></el-option>
    </el-select>
  </div>
</template>

<script>
export default {
  //表示不添加在组件的根元素上
  inheritAttrs: false,
  props: {
    setKey: {
      type: String,
      default:'key'
    },
    setLabel: {
      type: String,
      default: 'label'
    },
    setValue: {
      type: String,
      default: 'value'
    },
    // 数据回填的值
    writeData: {
      type: String,
      default: ''
    }
  },
  
  data() {
    return {
      options: [],
      value: '',
      pageSize: 1,
      loadingFlag:true
    }
  },
  created() {
    if (this.writeData) {
      this.value = this.writeData
    }
    this.getList()
  },
  mounted() {
    // 获取dom节点
    const domElementNode = document.querySelector('.more-next-box .el-select-dropdown__wrap')
    // 注册下拉滚动事件
    domElementNode.addEventListener('scroll',  ()=>{
      const isBottom = domElementNode.scrollHeight - domElementNode.scrollTop <= domElementNode.clientHeight
      if (isBottom) {
        console.log('是否到底了')
        // 这里应该还有一个判断,总条数和当前列表中的数据做一个比较。
        // 这里我就不写了
        this.getList(this.pageSize += 1)
      }
    })
  },
  methods: {
    getList(index = 1) {
      this.loadingFlag=true
      let arr = []
      console.log('index', index)
      setTimeout(() => {
        for (let i = 0; i < 10; i++){
          arr.push({
            value: (index-1) *10 + i+'',
            label: '数据'+ (index - 1) * 10 + i
          })
        }
        this.options = this.options.concat(arr)
        // this.loadingFlag = false
      },1500)
    }
  },
}
</script>

<style lang="scss" scoped>
.el-select-dropdown__item.is-disabled{
  color: red;
}
</style>

尾声

如果你觉得,这篇文章写的不错,对你有用。
请给我点一个赞,感谢了。

相关文章: