一、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进行比较来判断是否继续加载(上一篇文章有简单使用),且如果在同一个页面多处使用该组件,又会有一些新的问题,等待更新~~
本文介绍了Vue中自定义指令的使用,通过实例展示了如何创建一个自定义指令以实现在Select下拉列表中实现滚动加载功能。文章详细说明了自定义指令的钩子函数和参数,并在实际项目中结合Vuex进行数据处理,以优化用户体验,避免因数据量大、接口请求慢导致的等待时间过长。
2万+

被折叠的 条评论
为什么被折叠?



