vue - 自定义指令实现select下拉列表滚动加载(1)

本文介绍了Vue中自定义指令的使用,通过实例展示了如何创建一个自定义指令以实现在Select下拉列表中实现滚动加载功能。文章详细说明了自定义指令的钩子函数和参数,并在实际项目中结合Vuex进行数据处理,以优化用户体验,避免因数据量大、接口请求慢导致的等待时间过长。

一、vue自定义指令详解
除了内置的指令,如:v-on、v-show、v-build、v-model,vue也允许自定义指令,供我们来对底层dom元素进行操作,创建一个指令,绝不仅仅是给一个元素或者一个页面使用,而是给整个项目进行使用。
1、全局注册指令:

//该指令用来滚动加载下拉列表,用法:v-loadMore
function install(Vue, options = {}) {
  //loadMore指令名
  Vue.directive('loadMore', {
   //el绑定的dom元素,也就是使用该自定义指令的组件
    bind(el, binding) {
      //el.querySelector 返回与指定的选择器组匹配的元素的后代的第一个元素
      let select_dom = el.querySelector('.el-select-dropdown .el-select-dropdown__wrap');
      select_dom.addEventListener('scroll', function () {
        //监听scroll滚动事件
        let height = this.scrollHeight - this.scrollTop <= this.clientHeight;
        if (height) {
          binding.value() //指令的绑定值
        }
      })
    }
  });
}

2、一个指令定义对象可以提供如下几个钩子函数:

* bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
* inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。
   ** inserted使用原理:
       1、new Vue()对象扫描到带有该自定义指令的元素,会自动调用inserted函数;
       2、new Vue()还会自动将当前指令所在的dom怨毒对象,传入inserted函数;
       3、可以在inserted函数中,为当前的dom元素执行很多的原生的dom操作。
* update:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新 (详细的钩子函数参数见下)。
* componentUpdated:指令所在组件的 VNode 及其子 VNode 全部更新后调用。
* unbind:只调用一次,指令与元素解绑时调用。

钩子函数参数:

*  el:指令所绑定的元素,可以用来直接操作 DOM。
*  binding:一个对象,包含以下 property:
  name:指令名,不包括 v- 前缀。
  value:指令的绑定值,例如:v-my-directive="1 + 1" 中,绑定值为 2。
  oldValue:指令绑定的前一个值,仅在 update 和 componentUpdated 钩子中可用。无论值是否改变都可用。
  expression:字符串形式的指令表达式。例如 v-my-directive="1 + 1" 中,表达式为 "1 + 1"。
  arg:传给指令的参数,可选。例如 v-my-directive:foo 中,参数为 "foo"。
  modifiers:一个包含修饰符的对象。例如:v-my-directive.foo.bar 中,修饰符对象为 { foo: true, bar: true }。
  vnode:Vue 编译生成的虚拟节点。
  oldVnode:上一个虚拟节点,仅在 update 和 componentUpdated 钩子中可用。

3、如何使用自定义指令:代码演示:实现name的下拉框滚动加载
下面的功能实现,我将接口请求和数据放在了vuex里面封装,下面直接进行使用展示

<template>
  <div class="base-name-select">
  <!--v-loadMore 自定义下拉列表滚动加载的使用演示 -->
    <el-select
      size="small"
      :style="selectStyle"
      filterable
      v-model="selectName"
      v-loadMore="loadMoreNameData"
      placeholder="Please select name"
      :allow-create="allowCreate"
      :multiple="multiple"
      @change="handleSelectNameChange"
      :filter-method="filterMethod"
      :disabled="nameList.length == 0"
      clearable
    >
      <el-option
        v-for="(item, index) in nameList"
        :key="index"
        :value="item.name"
        :label="item.name"
      >
        {{ item.name }}
      </el-option>
    </el-select>
  </div>
</template>

<script>
import { mapState, mapActions, mapMutations } from 'vuex';
import debounce from 'lodash/debounce';
export default {
  name: 'BaseNameSelect',
  components: {},
  props: {
    //样式
    selectStyle: {
      type: Object,
      default: () => ({ width: '205px' }),
      required: false,
    },
    //提示信息
    placeholder: {
      type: String,
      default: '',
      required: false,
    },
    //是否多选
    multiple: {
      type: Boolean,
      default: false,
      required: false,
    },
    // 是否允许自行创建
    allowCreate: {
      type: Boolean,
      default: false,
      required: false,
    },
    pid: { //外部传参,用于该组件请求后端name数据的参数
      type: Number, 
      default: 0,
      required: false,
    },
    //是否禁用,如果外部传参需要的话,可以在组件disabled属性中增加改判断条件
    isDisabled: {
      type: Boolean,
      default: false,
      required: false,
    }
  },
  data: function() {
    return {
      pageIndex: 1,
      pageSize: 10,
      total: 0, //name数据后端传的总条数
      filter: '', //用于查询搜索
      selectName: '',
    };
  },
  computed: {
    //vuex中的变量
    ...mapState({
      nameList: state => state.jobNames.nameList,
      projectId: state => state.project.projectId,
      isNameSelectLoading: state => state.jobNames.isNameSelectLoading,
    }),
  },
  watch: {},
  created() {
    this.getNameListPagination('', false);
  },
  mounted() {},
  methods: {
    ...mapMutations({
      setNameList: 'jobNames/setNameList',
    }),

    ...mapActions({
    //接口请求方法
      getNameListData: 'jobNames/getNameListData',
    }),
    //滚动到底部时会触发的函数
    loadMoreNameData() {
      //如果当前的list的长度与后端返回的满足该pid的所有数据即total相等,说明没有数据,不必再进行接口请求
      //该判断只能用于后端接口请求后返回的total是满足该条件的所有数据的数量
      //如果total未知,可以看我的上一篇文章如何进行判断
      if (this.nameList && this.nameList.length == this.total) {
        return;
      }
      //如果不想等,说明还有下一页数据,即pageIndex页数++
      this.pageIndex++;
      this.pageSize = 10;
      //调用接口请求
      this.getNameListPagination(this.filter, this.pageIndex > 1 ? true : false);
    },

    getNameListPagination(name, loadMore) {
      let req = {
        pageIndex: this.pageIndex,
        pageSize: this.pageSize,
        search: name ? name : " '', //搜索过滤条件
      };
      if (!this.pid) { //如果外部没有传这个参数,即不进行接口请求,可省略
        return;
      }
      let params = {
        //请求参数
        id: this.pid,
        req: req,
      };
      //接口请求
      this.getNameListData(params).then(resp => {
        if (resp) {
        //先将本次接口请求的数据赋值给data
          let data = resp.data.names ? resp.data.names : [];
          if (loadMore) {
          //如果loadMore为true,说明是滚动加载请求接口,将该次请求的数据和之前的列表数据合并,concat数组合并的函数
            data = this.nameList.concat(data);
          }
          this.setNameList(data); //将得到的数据写到store中,如果实在本组件中定义的,即this.nameList = data赋值即可
          this.pageIndex = resp.extra.curPageIndex; //更新pageIndex
          this.total = resp.extra.total; //更新total,表示后端所有数据
        }
      });
    },

    filterMethodDebounce: debounce(function() {
      this.pageIndex = 1;
      this.pageSize = 10;
      this.getNameListPagination(this.filter, false);
    }, 500),

    filterMethod(filter) {
      this.filter = filter ? filter : '';
      this.filterMethodDebounce();
    },

    handleSelectBranchChange() {
      this.$emit('value-change', this.selectName);
    },

    setDefaultBranch(value) {
      //设置默认值
      if (value) {
        this.selectName = value;
      }
    },
  },
};
</script>

<style></style>

二、总结
了解自定义指令的原理和使用,其实在工作中,也会有用到的场景,该功能是我们后端需要去gitlab上拉数据,gitlab上数据较多且接口请求也比较慢,因此做滚动加载,这样不会让用户在使用系统时等待过久的时间,造成用户不好的体验感。这个方法是我最初的版本,后面由于后端在请求gitlab也是一页数据一页数据去请求,gitlab没有返回total,后端接口无法得到准确的total,后面改成了使用当前接口返回的数据数量与pageSize进行比较来判断是否继续加载(上一篇文章有简单使用),且如果在同一个页面多处使用该组件,又会有一些新的问题,等待更新~~

评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值