常见指令
vue中的指令,vue中都是以v-开头 (一般用来操作dom
)
v-once
渲染一次 (可用作优化,但是使用频率极少)v-html
将字符串转化成dom
插入到标签中 (会导致xss攻击问题,并且覆盖子元素)v-if/v-else/v-else-if
不满足时dom
不存在(可以使用template标签)v-show
不满足时dom
隐藏 (不能使用template标签)v-for
循环字符串、对象、数字、数组 (循环时必须加key,尽量不采用索引)v-bind
可以简写成: 属性(style、class…)绑定v-on
可以简写成@ 给元素绑定事件 (常用修饰符 .stop、.prevent、.self、.once、.passive)v-model
双向绑定 (支持.trim、.number修饰符)
自定义指令
指令钩子
指令钩子函数
一个指令定义对象可以提供如下几个钩子函数 (均为可选):
-
bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
-
inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。
-
update:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新。
-
componentUpdated:指令所在组件的 VNode 及其子 VNode 全部更新后调用。
-
unbind:只调用一次,指令与元素解绑时调用。
Vue.directive('gqs',{
bind() {
// 当指令绑定到 HTML 元素上时触发.**只调用一次**
console.log('bind triggerd')
},
inserted() {
// 当绑定了指令的这个HTML元素插入到父元素上时触发(在这里父元素是 `div#app`)**.但不保证,父元素已经插入了 DOM 文档.**
console.log('inserted triggerd')
},
updated() {
// 所在组件的`VNode`更新时调用.
console.log('updated triggerd')
},
componentUpdated() {
// 指令所在组件的 VNode 及其子 VNode 全部更新后调用。
console.log('componentUpdated triggerd')
},
unbind() {
// 只调用一次,指令与元素解绑时调用.
console.log('unbind triggerd')
}
})
指令钩子函数参数
-
每个钩子函数有以下参数:
- 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 钩子中可用。
-
注意:bind和insert都只调用一次;bind在insert前执行,也就是bind在dom树绘制前调用,insert在dom树绘制后调用;涉及dom操作的,我们一般都用insert,如自动获取焦点指令,只能用insert函数。
v-click-outside
推荐插件:https://www.npmjs.com/package/v-click-outside
点击区域以外关闭
src
├─ App.vue
└─ main.js
main.js
import Vue from 'vue'
import App from './App.vue'
Vue.config.productionTip = false
Vue.directive('clickOutside',{
bind(el,bindings,vnode){
el.handler = function (e) {
if(!el.contains(e.target)){
let method = bindings.expression;
// 触发指定绑定的方法
vnode.context[method]();
}
}
document.addEventListener('click',el.handler)
},
unbind(el){
document.removeEventListener('click',el.handler)
}
})
new Vue({
render: h => h(App),
}).$mount('#app')
App.vue
<template>
<div>
<div v-click-outside="blur">
<input type="text" @focus="focus">
<div v-show="visible">显示面板</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
visible: false,
};
},
created() {},
mounted() {},
methods: {
focus() {
this.visible = true;
},
blur() {
this.visible = false;
},
},
};
</script>
<style scoped lang="less">
</style>
v-lazy
- 官方推荐:https://www.npmjs.com/package/vue-lazyload
vue-lazyload.js
// 获取滚动区域的父元素
const getScrollParent = (el) => {
let parent = el.parentNode;
while (parent) {
if (/scroll/.test(getComputedStyle(parent)['overflow'])) {
return parent;
}
parent = parent.parentNode;
}
return parent;
}
const loadImageAsync = (src, resolve, reject) => {
let image = new Image();
image.src = src;
image.onload = resolve;
image.onerror = reject
}
const Lazy = (Vue) => {
// 每一个图片元素,都构造成一个类的实例
class ReactiveListener {
constructor({ el, src, elRenderer, options }) {
this.el = el;
this.src = src;
this.elRenderer = elRenderer;
this.options = options;
// 定义状态
this.state = { loading: false }
}
// 检测图片是否在可视区内
checkInView() {
let { top } = this.el.getBoundingClientRect();
return top < window.innerHeight * this.options.preLoad
}
load() {
// 先加载loading
this.elRenderer(this, 'loading');
// 如果加载成功,显示正常图片
loadImageAsync(this.src, () => {
this.state.loading = true; // 加载完毕了
this.elRenderer(this, 'loadinged');
}, () => {
this.elRenderer(this, 'error');
});
}
}
return class LazyClass {
constructor(options) {
// 保存用户传入的属性
this.options = options;
// 记录是否绑定过处理函数
this.bindHandler = false
// 存储元素实例
this.listenerQueue = []
}
handleLazyLoad() {
// 检查是否应该显示图片 计算当前图片是否在可视区
this.listenerQueue.forEach(listener => {
// 默认不在区域
if (!listener.state.loading) {
// 没有加载则加载
let catIn = listener.checkInView()
catIn && listener.load()
}
})
}
add(el, bindings, vnode) {
// 找到父亲元素(包含overflow属性的元素)
Vue.nextTick(() => {
// 带有滚动的盒子
let scrollParent = getScrollParent(el)
// 获取链接
let src = bindings.value;
if (!this.bindHandler) {
// 绑定事件
this.bindHandler = true
scrollParent.addEventListener('scroll', this.handleLazyLoad.bind(this))
}
// 判断当前元素是否容器可视区内,如果不是则不用渲染
const listener = new ReactiveListener({
el,
src,
options: this.options,
elRenderer: this.elRenderer.bind(this)
})
this.listenerQueue.push(listener)
// 判断数组里图片,哪些需要显示,哪些不需要显示
this.handleLazyLoad()
})
}
// 渲染图片
elRenderer(listener, state) {
let el = listener.el;
let src = '';
switch (state) {
case 'loading':
src = listener.options.loading || ''
break;
case 'error':
src = listener.options.error || ''
default:
src = listener.src;
break;
}
el.setAttribute('src', src)
}
}
}
const VueLazyload = {
install(Vue, options) {
const LazyClass = Lazy(Vue);
const lazy = new LazyClass(options);
Vue.directive('lazy', {
bind: lazy.add.bind(lazy)
});
}
}
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script src="https://cdn.bootcdn.net/ajax/libs/vue/2.7.8/vue.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/axios/1.7.2/axios.js"></script>
<script src="./vue-lazyload.js"></script>
<div id="app">
<div class="box">
<li v-for="img in imgs" :key="img">
<img v-lazy="img" >
</li>
</div>
</div>
<script>
const loading = 'http://localhost:3000/images/0.gif';
Vue.use(VueLazyload, {
preLoad: 0.3, // 可见区域的1.3倍
loading, // loading图
})
const vm = new Vue({
el: '#app',
data() {
return {
imgs: []
}
},
created() {
axios.get('http://localhost:3000/api/img').then(({ data }) => {
this.imgs = data;
})
}
});
</script>
<style>
.box {
height: 300px;
overflow: scroll;
width: 200px;
}
img {
width: 100px;
height: 100px;
}
</style>
</body>
</html>