Vue的指令中如何传递更多参数
概要我们在使用Vue的开发项目中,经常用自定义指令(directive)来封装一系列的DOM操作,这样做非常方便。一般来说,指令是使用动态指令参数来获取App中的数据。但是有些时候,自定义指令需要更多的数据来完成更复杂的功能,例如在指令中调用当前App实例的nextTick方法,以确保所有DOM元素加载完成,再进行DOM操作。还有一些情况,我们需要将一些全局配置参数传递给指令,已有的参数专递方式,
概要
我们在使用Vue的开发项目中,经常用自定义指令(directive)来封装一系列的DOM操作,这样做非常方便。一般来说,指令是使用动态指令参数来获取App中的数据。
但是有些时候,自定义指令需要更多的数据来完成更复杂的功能,例如在指令中调用当前App实例的nextTick方法,以确保所有DOM元素加载完成,再进行DOM操作。还有一些情况,我们需要将一些全局配置参数传递给指令,已有的参数专递方式,显然无法满足这些需求。
本文介绍一种扩展指令参数的方法,使其可以接收更多参数。该方法在Vue 2.0和 Vue 3.0中,都可以正常使用。
基本原理
本文介绍的指令扩展方法,主要以闭包为基础,并且使用了一些函数参数柯里化的方式来管理多个参数的传递过程。
我们以Vue2.0的指令定义方式为例,说明基本原理。本文所使用的指令定义方式,都已基于插件化的定义方式,在main.js中,通过use方法使用。
示例代码如下:
const myDirective = {
install(app,options){
app.directive("img-load", {
bind:function(el,binding,vnode){ },
inserted:function(el,binding,vnode){ },
update:function(el,binding,vnode){ },
componentUpdated:function(el,binding,vnode){ },
unbind:function(el,binding,vnode){ },
});
}
};
export default myDirective ;
按照上述标准的指令定义方式,无论使用哪个钩子函数,我们只能传递三个参数,指令所绑定的DOM元素,指令接收的APP中绑定参数和虚拟节点。
基于闭包的扩展方案
指令的钩子函数参数已经固定,我们无法修改。但是我们可以通过闭包设置钩子函数的作用域,让闭包函数来接收更多参数。
代码如下:
export default function getMyDirective(Vue) {
return class MyDirective{
constructor(options) {
this.options = options;
this.bindDirective= this.bindDirective.bind(this);
}
bindDirective(el, bindings) {
}
}
}
const myDirective = {
install(app,options){
const DirectiveClass = getMyDirective(app) ;
var myDirective = new DirectiveClass(options);
app.directive("my-dirctive", {
bind:myDirective.bindDirective
});
}
};
- 使用闭包函getMyDirective来包裹钩子函数bindDirective
- 闭包函数是用户自定义函数,我们可以设置任意多个参数
- 在闭包函数中定义类来封装指令的所有操作,构造方法也可以接收参数,从而将多个参数柯里化分割。
- 通过bind方法强行将指令钩子函数绑定的bindDirective方法的this限定为MyDirective的实例,也就是说,bindDirective方法可以通过this访问更多的数据。
JS中函数具有独立作用域,所以指令的绑定方法bindDirective在执行过程中,可以在不受任何外界其他代码的干扰下,使用闭包函数传递的参数。
实例和代码实现
本文以一个图片自动加载的指令为例,介绍自定义指令的参数扩展方式。
自定义指令的基本功能是根据图片的URL地址加载并显示图片,具体实现包括:
- 通过指令动态参数获取图片地址
- 首先在页面中显示一个正在加载的图片
- 加载指定地址图片,如果加载成功,正常显示
- 加载失败,显示一张加载出错的图片
本文以自顶向下的方式来介绍该实例的代码实现
Main.js中将指令对应的插件全局化
使用use方法,在全局定义插件ImageLoad,该插件主要是功能是在全局定义一个图片加载指令,为该指令接收一个全局配置,即加载中图片地址和加载失败的图片地址。
Vue.use.use(ImageLoad, {
loading: "http://localhost:4000/images/loading.gif",
error: "http://localhost:4000/images/error.jpeg",
});
ImageLoad插件定义
ImageLoad插件和其他插件一样,既然要通过use使用,所以要定义install方法,install方法的第一个参数是当前App实例,第二个则是指令的全局配置。
import getImageLoad from './getImageLoad'
const ImageLoad = {
install(app,options){
const ImgClass = getImageLoad(app) ;
var loadImage = new ImgClass(options);
app.directive("img-load", {
bind: loadImage.bindImage
});
}
};
export default ImageLoad;
- install方法中,首先通过调用getImageLoad方法,获取加载图片的管理类,传入当前App实例。
- 实例化图片加载管理类的对象loadImage ,传入图片加载的全局配置。
- 定义自定义指令v-img-load,该指定的bind钩子方法指向loadImage中的bindImage方法。
- bindImage方法的this是指向loadImage对象,因此可以使用到App实例,指令全局配置,loadImage对象内的数据。
图片加载管理类的定义
ImageLoadManagement定义了v-img-load指令的全部实现。
export default function getImageLoad(Vue) {
return class ImageLoadManagement {
constructor(options) {
this.options = options;
this.bindImage = this.bindImage.bind(this);
this.renderImage = this.renderImage.bind(this);
}
bindImage(el, bindings) {
const self = this;
Vue.nextTick(function(){
const src = bindings.value;
self.renderImage('loading', src, el);
self.loadImage(src).then(
() => self.renderImage('', src, el),
() => self.renderImage('error', src, el),
);
});
}
loadImage(src) {
return new Promise((resolve, reject) => {
const img = new Image();
img.src = src;
img.onload = resolve;
img.onerror = reject;
});
}
renderImage(type, src, el) {
let _src;
const {
error,
loading
} = this.options;
switch (type) {
case 'loading':
_src = loading;
break;
case 'error':
_src = error;
break;
default:
_src = src;
break;
}
el.setAttribute("src", _src);
}
}
}
为了避免参数过多,所以采用柯里化的方法,对参数进行了分割:
- 在闭包函数getImageLoad中,定义了全局App实例参数;
- ImageLoadManagement 类的构造方法中定义了图片加载指令需要的全局配置参数。
- class 作为function的语法糖使用,其本质来时function,从而实现独立作用域。
- bindImage方法中可以直接使用App实例的nextTick,无论该指令在父组件还是子组件中使用,都可以保证在指令中代码执行时,所有DOM元素加载完成。
- loadImage方法用于检查指定URL的图片是否存在,如果存在则显示具体图片,否则则显示加载失败的图片。
- renderImage方法用于设置指令绑定的Img元素的图片地址,图片的实际地址可以通过bindings参数的value属性获取。
通过上述方法,我们不仅扩展了指令的参数,使其可以支持更复杂的业务逻辑。
更重要的是。我们实现了指令的定义和实现逻辑的解耦,完全不再需要将所有的指令实现逻辑全部放在指令的注册方法中。通过ImageLoadManagement 的定义,将所有的指令实现逻辑都内聚在其中。
Vue 3.0的实现
Vue 3.0中,指令参数的扩展方法思路与2.0一致,只是因为Vue 3.0中指令的钩子函数名称与2.0不一致,造成一些区别。具体代码如下:
import getImageLoad from './getImageLoad'
const ImageLoad = {
install(app,options){
const ImgClass = getImageLoad(options) ;
var loadImage = new ImgClass();
app.directive("img-load", {
mounted: loadImage.bindImage
});
}
};
export default ImageLoad;
export default function getImageLoad(options) {
return class ImageLoadManagement {
constructor() {
this.options = options;
this.bindImage = this.bindImage.bind(this);
this.renderImage = this.renderImage.bind(this);
}
bindImage(el, bindings) {
const src = bindings.value;
this.renderImage('loading', src, el);
this.loadImage(src).then(
() => this.renderImage('', src, el),
() => this.renderImage('error', src, el),
);
}
loadImage(src) {
return new Promise((resolve, reject) => {
const img = new Image();
img.src = src;
img.onload = resolve;
img.onerror = reject;
});
}
renderImage(type, src, el) {
let _src;
const {
error,
loading
} = this.options;
switch (type) {
case 'loading':
_src = loading;
break;
case 'error':
_src = error;
break;
default:
_src = src;
break;
}
el.setAttribute("src", _src);
}
}
}
更多推荐
所有评论(0)