您好,登錄后才能下訂單哦!
本篇內容主要講解“怎么寫一個Vue3的自定義指令”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“怎么寫一個Vue3的自定義指令”吧!
眾所周知,Vue.js 的核心思想是數據驅動 + 組件化,通常我們開發頁面的過程就是在編寫一些組件,并且通過修改數據的方式來驅動組件的重新渲染。在這個過程中,我們不需要去手動操作 DOM。
然而在有些場景下,我們還是避免不了要操作 DOM。由于 Vue.js 框架接管了 DOM 元素的創建和更新的過程,因此它可以在 DOM 元素的生命周期內注入用戶的代碼,于是 Vue.js
設計并提供了自定義指令,允許用戶進行一些底層的 DOM 操作。
舉個實際的例子——圖片懶加載。圖片懶加載是一種常見性能優化的方式,由于它只去加載可視區域圖片,能減少很多不必要的請求,極大的提升用戶體驗。
而圖片懶加載的實現原理也非常簡單,在圖片沒進入可視區域的時候,我們只需要讓 img 標簽的 src 屬性指向一張默認圖片,在它進入可視區后,再替換它的 src 指向真實圖片地址即可。
如果我們想在 Vue.js 的項目中實現圖片懶加載,那么用自定義指令就再合適不過了,那么接下來就讓我手把手帶你用 Vue3 去實現一個圖片懶加載的自定義指令 v-lazy。
為了讓這個指令方便地給多個項目使用,我們把它做成一個插件:
const lazyPlugin = { install (app, options) { app.directive('lazy', { // 指令對象 }) } } export default lazyPlugin
然后在項目中引用它:
import { createApp } from 'vue' import App from './App.vue' import lazyPlugin from 'vue3-lazy' createApp(App).use(lazyPlugin, { // 添加一些配置參數 })
通常一個 Vue3 的插件會暴露 install 函數,當 app 實例 use 該插件時,就會執行該函數。在 install 函數內部,通過 app.directive 去注冊一個全局指令,這樣就可以在組件中使用它們了。
接下來我們要做的就是實現該指令對象,一個指令定義對象可以提供多個鉤子函數,比如 mounted
、updated
、unmounted
等,我們可以在合適的鉤子函數中編寫相應的代碼來實現需求。
在編寫代碼前,我們不妨思考一下實現圖片懶加載的幾個關鍵步驟。
圖片的管理:
管理圖片的 DOM、真實的 src、預加載的 url、加載的狀態以及圖片的加載。
可視區的判斷:
判斷圖片是否進入可視區域。
關于圖片的管理,我們設計了 ImageManager 類:
const State = { loading: 0, loaded: 1, error: 2 } export class ImageManager { constructor(options) { this.el = options.el this.src = options.src this.state = State.loading this.loading = options.loading this.error = options.error this.render(this.loading) } render() { this.el.setAttribute('src', src) } load(next) { if (this.state > State.loading) { return } this.renderSrc(next) } renderSrc(next) { loadImage(this.src).then(() => { this.state = State.loaded this.render(this.src) next && next() }).catch((e) => { this.state = State.error this.render(this.error) console.warn(`load failed with src image(${this.src}) and the error msg is ${e.message}`) next && next() }) } } export default function loadImage (src) { return new Promise((resolve, reject) => { const image = new Image() image.onload = function () { resolve() dispose() } image.onerror = function (e) { reject(e) dispose() } image.src = src function dispose () { image.onload = image.onerror = null } }) }
首先,對于圖片而言,它有三種狀態,加載中、加載完成和加載失敗。
當 ImageManager
實例化的時候,除了初始化一些數據,還會把它對應的 img 標簽的 src 執行加載中的圖片 loading
,這就相當于默認加載的圖片。
當執行 ImageManager
對象的 load 方法時,就會判斷圖片的狀態,如果仍然在加載中,則去加載它的真實 src,這里用到了 loadImage
圖片預加載技術實現去請求 src 圖片,成功后再替換 img 標簽的 src,并修改狀態,這樣就完成了圖片真實地址的加載。
有了圖片管理器,接下來我們就需要實現可視區的判斷以及對多個圖片的管理器的管理,設計 Lazy 類:
const DEFAULT_URL = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7' export default class Lazy { constructor(options) { this.managerQueue = [] this.initIntersectionObserver() this.loading = options.loading || DEFAULT_URL this.error = options.error || DEFAULT_URL } add(el, binding) { const src = binding.value const manager = new ImageManager({ el, src, loading: this.loading, error: this.error }) this.managerQueue.push(manager) this.observer.observe(el) } initIntersectionObserver() { this.observer = new IntersectionObserver((entries) => { entries.forEach((entry) => { if (entry.isIntersecting) { const manager = this.managerQueue.find((manager) => { return manager.el === entry.target }) if (manager) { if (manager.state === State.loaded) { this.removeManager(manager) return } manager.load() } } }) }, { rootMargin: '0px', threshold: 0 }) } removeManager(manager) { const index = this.managerQueue.indexOf(manager) if (index > -1) { this.managerQueue.splice(index, 1) } if (this.observer) { this.observer.unobserve(manager.el) } } } const lazyPlugin = { install (app, options) { const lazy = new Lazy(options) app.directive('lazy', { mounted: lazy.add.bind(lazy) }) } }
這樣每當圖片元素綁定 v-lazy 指令,且在 mounted
鉤子函數執行的時候,就會執行 Lazy 對象的 add 方法,其中第一個參數 el 對應的就是圖片對應的 DOM 元素對象,第二個參數 binding
就是指令對象綁定的值,比如:
<img class="avatar" v-lazy="item.pic">
其中 item.pic 對應的就是指令綁定的值,因此通過binding.value
就可以獲取到圖片的真實地址。
有了圖片的 DOM 元素對象以及真實圖片地址后,就可以根據它們創建一個圖片管理器對象,并添加到 managerQueue
中,同時對該圖片 DOM 元素進行可視區的觀察。
而對于圖片進入可視區的判斷,主要利用了 IntersectionObserver API,
它對應的回調函數的參數 entries,是 IntersectionObserverEntry
對象數組。當觀測的元素可見比例超過指定閾值時,就會執行該回調函數,對 entries 進行遍歷,拿到每一個 entry,然后判斷 entry.isIntersecting
是否為 true,如果是則說明 entry 對象對應的 DOM 元素進入了可視區。
然后就根據 DOM 元素的比對從 managerQueue
中找到對應的 manager
,并且判斷它對應圖片的加載狀態。
如果圖片是加載中的狀態,則此時執行manager.load
函數去完成真實圖片的加載;如果是已加載狀態,則直接從 managerQueue
中移除其對應的管理器,并且停止對圖片 DOM 元素的觀察。
目前,我們實現了圖片元素掛載到頁面后,延時加載的一系列處理。不過,當元素從頁面卸載后,也需要執行一些清理的操作:
export default class Lazy { remove(el) { const manager = this.managerQueue.find((manager) => { return manager.el === el }) if (manager) { this.removeManager(manager) } } } const lazyPlugin = { install (app, options) { const lazy = new Lazy(options) app.directive('lazy', { mounted: lazy.add.bind(lazy), remove: lazy.remove.bind(lazy) }) } }
當元素被卸載后,其對應的圖片管理器也會從 managerQueue
中被移除,并且停止對圖片 DOM 元素的觀察。
此外,如果動態修改了 v-lazy 指令綁定的值,也就是真實圖片的請求地址,那么指令內部也應該做對應的修改:
export default class ImageManager { update (src) { const currentSrc = this.src if (src !== currentSrc) { this.src = src this.state = State.loading } } } export default class Lazy { update (el, binding) { const src = binding.value const manager = this.managerQueue.find((manager) => { return manager.el === el }) if (manager) { manager.update(src) } } } const lazyPlugin = { install (app, options) { const lazy = new Lazy(options) app.directive('lazy', { mounted: lazy.add.bind(lazy), remove: lazy.remove.bind(lazy), update: lazy.update.bind(lazy) }) } }
至此,我們已經實現了一個簡單的圖片懶加載指令,在這個基礎上,還能做一些優化嗎?
指令的優化
在實現圖片的真實 url 的加載過程中,我們使用了 loadImage
做圖片預加載,那么顯然對于相同 url 的多張圖片,預加載只需要做一次即可。
為了實現上述需求,我們可以在 Lazy 模塊內部創建一個緩存 cache:
export default class Lazy { constructor(options) { // ... this.cache = new Set() } }
然后在創建 ImageManager
實例的時候,把該緩存傳入:
const manager = new ImageManager({ el, src, loading: this.loading, error: this.error, cache: this.cache })
然后對 ImageManager 做如下修改:
export default class ImageManager { load(next) { if (this.state > State.loading) { return } if (this.cache.has(this.src)) { this.state = State.loaded this.render(this.src) return } this.renderSrc(next) } renderSrc(next) { loadImage(this.src).then(() => { this.state = State.loaded this.render(this.src) next && next() }).catch((e) => { this.state = State.error this.cache.add(this.src) this.render(this.error) console.warn(`load failed with src image(${this.src}) and the error msg is ${e.message}`) next && next() }) } }
在每次執行 load
前從緩存中判斷是否已存在,然后在執行 loadImage
預加載圖片成功后更新緩存。
通過這種空間換時間的手段,就避免了一些重復的 url 請求,達到了優化性能的目的。
到此,相信大家對“怎么寫一個Vue3的自定義指令”有了更深的了解,不妨來實際操作一番吧!這里是億速云網站,更多相關內容可以進入相關頻道進行查詢,關注我們,繼續學習!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。