vue自定义指令-1

·  阅读 320

前言

前段时间做的项目中有一些vue自定义指令,但是却没有封装的很好,今天抽空去改造了一下,写完之后又觉得一个指令少的可怜不好意思拿出手,然后就疯狂脑补一些场景,结合网上搜寻的资料,又写了几个指令,然后就记录一下,以后会用到。本文面向有vue基础的读者,不会对于自定义指令的基础知识做太多的解释,可以参照vue的官方文档进行学习。文中自定义的指令用了ts的语法,末尾会有一份对于还没接触过ts语法的说明。

开始

作为一个混迹码场好几年的前端cv工程师,如果不懂vue的自定义指令,那明天就有可能因为左脚先踏进公司大门而被炒了。vue3官方的介绍中有这么一句话:自定义指令主要是为了重用涉及普通元素的底层 DOM 访问的逻辑。

谈谈个人理解

Vue推崇数据驱动视图的理念,具体一点是Vue推崇使用框架的人可以单纯的用数据驱动视图,因为写Vue的人帮你去操作了DOM,但并非所有情况都适合数据驱动。所以推出了自定义指令来让使用者更优雅的实现一些可复用的 DOM 访问的逻辑,毕竟你在 methods 中写一堆操作 DOM 的逻辑有对 vue 侮辱的嫌疑。所以:vue自定义指令,核心就是需要自己亲手去操作DOM。

vue有一些内置的指令,比如 v-if 、 v-model ... 背过面试题吧,灵魂拷问一下:v-model的原理是什么?hhhh,背出来的时候是不是理解了一些东西,这些内置指令不就是vue在底层帮你操作了DOM嘛!所以自定义指令,就是自己亲手对原生操作dom进行一次封装!!!

前置工作

一开始在 main.ts 中写指令,发现很臃肿。然后就改造了一下,把逻辑放到外面,然后一顿导出导入操作之后,只需要在 main.ts 注册指令,但是两三个指令之后屎山又重现了。然后继续优化,就有了自我认为的优雅方式,给客官上图:

  • 创建directives文件夹管理自定义指令
  • directives/modules存放自定义指令的逻辑
  • index.ts 用来注册 全局自定义 指令

image.png

  • main.ts中直接导入index.ts,然后use即可

image.png

自定义指令之图片懒加载

对于电商、资讯等类型的应用,一大堆图片的展示是必不可少的,而通常的图片都不会太小,更何况一个页面不下几十张图,所以对于图片的优化是必不可少的,方式很多,比如加强压缩,图片压缩得越小,网络请求得压力也就越小。但是这并不能满足,因为再小的图,它总归要下载,多了还是一样的造成性能不良。那么能不能让它体积更小的同时让它不下载,那性能不就嘎嘎提升了嘛!不下载???那我就看不到虎牙妹了嘛(桫哥喜欢放的一张图。2207路过),不下载就看不到,下载了又消耗性能,咋整?那就折个中,你能看到就下载,看不到就不加载。所以,看到才加载图片,就是懒加载

说了那么多,其实总结出来就两个点

  1. 浏览器怎么知道我们什么时候看到(图该在什么时机加载)
  2. 去哪里加载图片资源(src里面肯定不是真实地址,不然渲染页面的时候就会加载图片了)

先解决第一点,浏览器怎么知道我们什么时候看到,学过原生js应该都知道有一些视口高度、浏览器卷曲的距离、元素距离顶部的高度等等,灵魂拷问又来了:你还记得这些API是啥么?

image.png

图片和顶部的距离 < 视口高度+滚动条卷曲高度 => 说明在可视窗口内,要加载图片

图片和顶部的距离 > 视口高度+滚动条卷曲高度 => 说明在可视窗口外,不加载图片

解决第二点:去哪里加载图片资源,前面说了src里面肯定不是真实地址,不然渲染页面的时候就会加载图片了,所以我们的解决方式也很简单:把真实的图片地址放到自定义属性中去,等该加载的时候将真实地址放到 src 中就完事了

<img src="loading图片资源路径" data-url="真实图片路径" alt="">
复制代码

说完思路和步骤,来总结一下那句话:自定义指令,就是自己亲手对原生操作dom进行一次封装!!! 以上无论是获取高度还是设置自定义指令等等,都是对DOM的操作!

嘿嘿,说了这么多,其实最后我没用这个方式,因为它很简单但是又不优雅。只是提供一个思路,我用了IntersectionObserver这个API,有兴趣可以去查一下,官方上说明是提供了一种异步观察目标元素与其祖先元素或顶级文档视窗(viewport)交叉状态的方法。简单来说当观察的元素出现在视口的时候执行一个回调函数,看代码吧

/**
 * v-imglazy
 * 图片懒加载
 * 在img标签结构父级上面使用
 */
 import type { Directive } from "vue";
 // ts 类型限制,不懂就忽略
 interface ElType extends HTMLElement {
     __cb__: () => any;
 }

 const imglazy: Directive = {
     mounted(el: ElType) {
        el.classList.add("lyclass")
        const imgs = document.querySelectorAll(".lyclass img")
        const cb = (entires:any) => {
            //图片进入视口时就执行回调
            entires.forEach((item:any) => {
                // 获取目标元素
                let oImg = item.target
                // 当图片进入视口的时候,就赋值图片的真实地址
                if (item.isIntersecting) {
                    oImg.setAttribute('src', oImg.getAttribute('data-img'))
                    // 移除自定义属性'data-url' 可以不移除 无所谓的
                    oImg.removeAttribute('data-img')
                    // 这里资源加载后就停止进行观察
                    io.unobserve(item.target)
                }
            })
        }
        let io = new IntersectionObserver(cb)
        Array.from(imgs).forEach(element => {
            //给每一个图片设置监听
            io.observe(element)
        });
     }
 };
 
 export default imglazy;
复制代码

然后在 index.ts 中注册

// 引入
import clickcopy from "./modules/clickcopy";
import selectcopy from "./modules/selectcopy";
import waterMarker from "./modules/waterMarker";
import draggable from "./modules/draggable";
import debounce from "./modules/debounce";
import throttle from "./modules/throttle";
import longpress from "./modules/longpress";
import imglazy from "./modules/imglazy";
import permission from "./modules/permission";

const directivesList: any = {
	clickcopy,
	waterMarker,
	draggable,
	debounce,
	throttle,
	longpress,
	imglazy,
	permission,
	selectcopy
};

const directives = {
	install: function (app: any) {
		Object.keys(directivesList).forEach(key => {
			// 注册所有自定义指令
			app.directive(key, directivesList[key]);
		});
	}
};
// 导出
export default directives;
复制代码

main.ts 中注册

import { createApp } from 'vue'
import { createPinia } from 'pinia'

import App from './App.vue'
import router from './router'

import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'

import './assets/main.css'

// 这个
import directives from "@/directives/index";

const app = createApp(App)

app.use(createPinia())
app.use(router)
app.use(ElementPlus)

//这个
app.use(directives)

app.mount('#app')

复制代码

如果你还没接触TS,那就看下面这个吧,TS简单的理解可以是加了类型限制的JS,我们所用的JS弱类型语言,弱类型语言不是说JS是垃圾(有人这么认为的),它是说光从定义上你区分不出一个变量的类型,ES6之前我们都是一个 var 走天下,数值布尔字符串,统统的 var var var,这在开发中就容易出现错误,比如一个函数需要接受Number类型的参数,但是你传String也是可以的,只是结果会不同预期。而强类型语言诸如C语言,光Number类型就能给你分出整型浮点型等等类型数据,那就能减少这些低级错误。从这方面说JS是个**好像还能理解

简单的看就是这样的 let num: number 这样就限制了num只能为Number类型,后期想更改也只能是改成Number类型,否则代码就报红。那如果你还不了解的话凡是看到 :xxx 或者 interface 啥的你就不用管,不舒服就删掉,拿上面的演示一遍给你看

/**
 * v-imglazy
 * 图片懒加载
 * 在img标签结构父级上面使用
 */
 const imglazy = {
     mounted(el) {
        el.classList.add("lyclass")
        const imgs = document.querySelectorAll(".lyclass img")
        const cb = (entires) => {
            //图片进入视口时就执行回调
            entires.forEach((item) => {
                // 获取目标元素
                let oImg = item.target
                // 当图片进入视口的时候,就赋值图片的真实地址
                if (item.isIntersecting) {
                    oImg.setAttribute('src', oImg.getAttribute('data-img'))
                    // 移除自定义属性'data-url' 可以不移除 无所谓的
                    oImg.removeAttribute('data-img')
                    // 这里资源加载后就停止进行观察
                    io.unobserve(item.target)
                }
            })
        }
        let io = new IntersectionObserver(cb)
        Array.from(imgs).forEach(element => {
            //给每一个图片设置监听
            io.observe(element)
        });
     }
 };
 
 export default imglazy;
复制代码

这回看得懂了吧,TS怎么说呢,一种技术吧,你说重要,内卷找工作确实挺重要(别人会你不会那你就没机会)但是单纯对于项目来说有些时候并不重要,JS照样行得通,所以,对于一些很关心数据的项目,比如金融这些,应该用TS,但是别的嘛,就仁者见仁智者见智了,技术无贵贱,适合最重要。当然啦,这些技术选型是由你们的项目组长决定的,嘿嘿,能看到这里,说明有一天你也会成为那个考虑项目该用啥技术的人!是吧,各位前端大佬!

写到这感觉挺长了,接下来还有几个自定义指令我就新写一篇吧,拉的老长也不方便以后查阅。

分类:
前端
收藏成功!
已添加到「」, 点击更改