vue指令封装(按钮权限、loading加载、slideIn窗口进入动画)
vue 指令
vue 本身具有一些指令,但是有些指令是 vue 作者自己写的,有些是第三方插件写的。
v-if
v-if 指令是用来控制元素是否显示的,如果值为 true,则显示,如果值为 false,则隐藏。
<div id="app">
<p v-if="isShow">我是显示的内容</p>
<p v-else>我是隐藏的内容</p>
</div>
<script>
var vm = new Vue({
el: "#app",
data: {
isShow: true,
},
});
</script>
v-for
v-for 指令是用来遍历数组或者对象,遍历的结果会被渲染到页面上。
<div id="app">
<ul>
<li v-for="item in list">{{item.name}}</li>
</ul>
</div>
<script>
var vm = new Vue();
vm.list = [{ name: "张三" }, { name: "李四" }, { name: "王五" }];
vm.$mount("#app");
// vm.$mount('#app') // 也可以写成 vm.$mount('#app'),因为vm.$mount('#app')内部也是调用了vm.$mount('#app'),所以可以写成vm.$mount('#app'),因为vm.$mount('#app')内部也是调用了vm.$mount('#app'),所以可以写成vm.$mount('#app')
</script>
v-show
v-show 指令是用来控制元素是否显示的,如果值为 true,则显示,如果值为 false,则隐藏。
<div id="app">
<p v-show="isShow">我是显示的内容</p>
</div>
<script>
var vm = new Vue({
el: "#app",
data: {
isShow: true,
},
});
// 等价于
var vm = new Vue({
el: "#app",
data: {
isShow: true,
},
directives: {
show: {
update: function (value) {
this.el.style.display = value ? "" : "none";
},
},
},
});
</script>
v-text
v-text 指令是用来显示文本的,如果值为字符串,则显示,如果值为变量,则显示变量的值。
<div id="app">
<p v-text="msg"></p>
</div>
<script>
var vm = new Vue(
{
el: '#app',
}
msg: '我是文本'
)
</script>
v-html
v-html 指令是用来显示 html 的,如果值为字符串,则显示,如果值为变量,则显示变量的值。
<div id="app">
<p v-html="msg"></p>
</div>
<script>
var vm = new Vue(
{
el: '#app',
}
msg: '<span style="color: red">我是文本</span>'
)
</script>
v-bind
v-bind 指令是用来绑定元素属性的,如果值为字符串,则绑定元素的属性,如果值为变量,则绑定变量的值。
<div id="app">
<input type="text" v-bind:value="msg" />
</div>
<script>
var vm = new Vue(
{
el: '#app',
}
msg: '我是文本'
)
// 等价于
var vm = new Vue(
{
el: '#app',
}
msg: '我是文本'
)
</script>
v-on
v-on 指令是用来绑定元素事件的,如果值为字符串,则绑定元素的事件,如果值为变量,则绑定变量的值。
<div id="app">
<button v-on:click="showMsg">点我</button>
</div>
<script>
var vm = new Vue({
el: "#app",
methods: {
showMsg: function () {
alert("我是按钮");
},
},
});
</script>
v-bind:class
v-bind:class 指令是用来绑定元素的 class 属性的,如果值为字符串,则绑定元素的 class 属性,如果值为变量,则绑定变量的值。
<div id="app">
<div v-bind:class="isShow?'show' : 'hide'">
我是显示的内容
<span v-bind:class="isShow?'show' : 'hide'">我是隐藏的内容</span>
</div>
</div>
<script>
var vm = new Vue({
el: "#app",
data: {
isShow: true,
},
});
</script>
v-model
v-model 指令是用来双向绑定表单元素的,如果值为字符串,则绑定元素的 value 属性,如果值为变量,则绑定变量的值。
<div id="app">
<input type="text" v-model="msg" />
</div>
<script>
var vm = new Vue();
vm.msg = "我是文本";
vm.$mount("#app");
</script>
v-pre
v-pre 指令是用来跳过这个元素和它的子元素的编译过程。
<div id="app">
<p v-pre>我是文本</p>
</div>
<script>
var vm = new Vue();
vm.$mount("#app");
</script>
v-cloak
v-cloak 指令是用来隐藏元素直到编译完成。
<div id="app" v-cloak>
<p>我是文本</p>
</div>
<script>
var vm = new Vue();
vm.$mount("#app");
</script>
v-once
v-once 指令是用来只渲染元素和它的子元素一次。
<div id="app">
<p v-once>我是文本</p>
</div>
<script>
var vm = new Vue();
vm.$mount("#app");
</script>
v-pre
v-pre 指令是用来跳过这个元素和它的子元素的编译过程。
<div id="app">
<p v-pre>我是文本</p>
</div>
<script>
var vm = new Vue();
vm.$mount("#app");
</script>
v-cloak
v-cloak 指令是用来隐藏元素直到编译完成。
<div id="app" v-cloak>
<p>我是文本</p>
</div>
<script>
var vm = new Vue();
vm.$mount("#app");
</script>
打住,本来就是准备一个简单的介绍,不准备详细介绍这些指令,但是奈何 GPT 功能强大。@@!
自定义指令
自定义指令是一种代码重游的方式,可以将一些行为抽取出来,封装成一个指令,在元素上使用这个指令,就可以让页面上相应的行为发生变化。自定义指令主要是为了重用涉及普通元素的底层 DOM 访问的逻辑。
以下是一个简单的官方案例
选项式格式:
<template>
<input v-focus />
</template>
<script setup>
const focus = {
mounted: (el) => el.focus(),
};
export default {
directives: {
// 在模板中启用 v-focus
focus,
},
};
</script>
组合式格式:
<script setup>
// 在模板中启用 v-focus
const vFocus = {
mounted: (el) => el.focus(),
};
</script>
<template>
<input v-focus />
</template>
在
<script setup>
中,任何以 v 开头的驼峰式命名的变量都可以被用作一个自定义指令。在上面的例子中,vFocus 即可以在模板中以 v-focus 的形式使用。在没有使用<script setup>
的情况下,自定义指令需要通过 directives 选项注册
指令钩子
一个自定义指令由一个包含类似组件生命周期钩子的对象来定义。钩子函数会接收到指令所绑定元素作为其参数。
const myDirective = {
// 在绑定元素的 attribute 前
// 或事件监听器应用前调用
created(el, binding, vnode, prevVnode) {
// 下面会介绍各个参数的细节
},
// 在元素被插入到 DOM 前调用
beforeMount(el, binding, vnode, prevVnode) {},
// 在绑定元素的父组件
// 及他自己的所有子节点都挂载完成后调用
mounted(el, binding, vnode, prevVnode) {},
// 绑定元素的父组件更新前调用
beforeUpdate(el, binding, vnode, prevVnode) {},
// 在绑定元素的父组件
// 及他自己的所有子节点都更新后调用
updated(el, binding, vnode, prevVnode) {},
// 绑定元素的父组件卸载前调用
beforeUnmount(el, binding, vnode, prevVnode) {},
// 绑定元素的父组件卸载后调用
unmounted(el, binding, vnode, prevVnode) {},
};
这里需要注意的式指令的钩子函数,在调用时,会将当前元素、指令对象、虚拟节点、上一个虚拟节点作为参数传递给钩子函数。
指令参数
指令的参数可以是以下几种类型:
- 字符串:如果指令的值是一个字符串,则会被当作 CSS 类名来绑定到元素上。
- 对象:如果指令的值是一个对象,则会基于对象的值来设置元素的属性。
- 表达式:如果指令的值是一个表达式,则会基于表达式的值来设置元素的属性。
- 函数:如果指令的值是一个函数,则会调用该函数,并将函数的返回值作为指令的绑定值。
指令的绑定值
指令的绑定值是由指令的定义函数接收到的参数,在指令的钩子函数中,可以通过 binding.value
访问到。
const myDirective = {
//...
// 指令的绑定值
bind(el, binding, vnode, prevVnode) {
// 绑定值
console.log(binding.value);
},
};
指令的更新值
指令的更新值是由指令的更新函数接收到的参数,在指令的钩子函数中,可以通过 binding.value
访问到。
const myDirective = {
//...
// 指令的更新值
update(el, binding, vnode, prevVnode) {
// 更新值
console.log(binding.value);
},
};
指令的 oldValue
指令的 oldValue 是一个只读的属性,它的值就是指令的绑定值在上一次更新时的值。仅在beforeUpdate
和 updated
中可用。无论值是否更改,它都可用。
const myDirective = {
//...
// 指令的 oldValue
update(el, binding, vnode, prevVnode) {
// 更新值
console.log(binding.value);
if (binding.value) {
// 指令的 oldValue
console.log(binding.oldValue);
}
},
};
指令的 arg
指令的 arg 是一个只读的属性,它的值就是指令名之后的第一个冒号后面的值。
例如在 v-my-directive:foo 中,参数是 "foo"。
指令的 modifiers
指令的 modifiers 是一个只读的对象,它包含了指令名之后的所有的冒号后面的修饰符。
例如在 v-my-directive.foo.bar 中,修饰符对象是 { foo: true, bar: true }。
const myDirective = {
// 指令的 modifiers
update(el, binding, vnode, prevVnode) {
// modifiers
console.log(binding.modifiers);
},
};
指令的 vnode
指令的 vnode 是一个只读的对象,它包含了当前指令所绑定的虚拟节点。
const myDirective = {
// 指令的 vnode
update(el, binding, vnode, prevVnode) {
// vnode
console.log(binding.vnode);
},
};
指令的 prevNode
指令的 prevNode 是一个只读的对象,它包含了上一个虚拟节点。代表之前的渲染中指令所绑定元素的 VNode。仅在 beforeUpdate 和 updated 钩子中可用。
const myDirective = {
// 指令的 prevNode
update(el, binding, vnode, prevVnode) {
// prevNode
console.log(binding.prevNode);
},
};
举例来说,像下面这样使用指令:
<div v-example:foo.bar="baz"></div>
binding 参数会是一个这样的对象:
{
arg: 'foo',
modifiers: { bar: true },
value: /* `baz` 的值 */,
oldValue: /* 上一次更新时 `baz` 的值 */
}
除了 el 外,其他参数都是只读的,不要更改它们。若你需要在不同的钩子间共享信息,推荐通过元素的 dataset attribute 实现。
在指令钩子中,可以通过el.dataset
访问到元素的 dataset attribute。
简化指令
对于自定义指令来说,一个很常见的情况是仅仅需要在 mounted 和 updated 上实现相同的行为,这样可以使得指令更加简洁。
app.directive("color", (el, binding) => {
// 这会在 `mounted` 和 `updated` 时都调用
el.style.color = binding.value;
});
在组件上使用指令
当在组件上使用自定义指令时,它会始终应用于组件的根节点,和透传 attributes 类似。
<div id="app">
<my-component v-color="red"></my-component>
</div>
<!-- MyComponent 的模板 -->
<div>
<!-- 指令会被应用在此处 -->
<span>My component content</span>
</div>
vue 插件语法
一般我们会将指令做成 vue 插件,这样可以方便我们复用,并且可以避免一些全局变量污染。
vue 暴露出来一个扩展方法,install 方法里面可以直接添加插件。
export default {
install(app) {
//...
},
};
封装按钮权限指令
app.directive("permisson", {
mounted(el, binding) {
if (!binding.value) return false;
const userStore = useUserStore();
if (!userStore.buttons.includes(binding.value)) {
el.parentNode.removeChild(el);
}
},
});
封装加载动画指令
app.directive("preload", (el, binding) => {
el.style.position = "relative";
const vnode = h(FxLoading, {
isShow: binding.value,
sec: binding.arg && parseInt(binding.arg),
});
render(vnode, el);
});
封装窗口元素进入动画
//动画距离
const DISTANCE = 100; 、
//动画时间
const DURATION = 500;
// 动画对应关系映射
const animationMap = new WeakMap();
// 窗口监视器
const ob = new IntersectionObserver((entires) => {
for (const entry of entires) {
if (entry.isIntersecting) {
animationMap.get(entry.target).play();
ob.unobserve(entry.target);
}
}
});
/**
* 判断给定的元素是否在视图范围之外
* @param {HTMLElement} el - 要判断的元素
* @returns {boolean} - 如果元素在视图范围之外则返回true,否则返回false
*/
function isBelowViewPort(el) {
const rect = el.getBoundingClientRect();
return rect.top > window.innerHeight;
}
app.directive("slideIn", {
mounted(el) {
setTimeout(() => {
if (!isBelowViewPort(el)) {
// 只有当元素在视口top值下面的时候才会触发animate
return;
}
const animation = el.animate(
[
{
transform: `translateY(${DISTANCE}px)`,
opacity: 0.5,
},
{
transform: `translateY(0px)`,
opacity: 1,
},
],
{
duration: DURATION,
easing: "ease",
}
);
animation.pause();
animationMap.set(el, animation);
ob.observe(el);
}, 0);
},
unmounted(el) {
ob.unobserve(el);
},
});