亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

如何寫(xiě)一個(gè) Vue3 的自定義指令

 更新時(shí)間:2022年01月07日 10:08:33   作者:前端巔峰?  
這篇文章主要介紹了如何寫(xiě)一個(gè) Vue3 的自定義指令,如果我們想在 Vue.js 的項(xiàng)目中實(shí)現(xiàn)圖片懶加載,那么用自定義指令就再合適不過(guò)了,那么接下來(lái)就讓我手把手帶你用 Vue3 去實(shí)現(xiàn)一個(gè)圖片懶加載的自定義指令 v-lazy,需要的朋友可以參考一下

前端巔峰 
以下文章來(lái)源于微信公眾號(hào)前端巔峰

背景

眾所周知,Vue.js 的核心思想是數(shù)據(jù)驅(qū)動(dòng) + 組件化,通常我們開(kāi)發(fā)頁(yè)面的過(guò)程就是在編寫(xiě)一些組件,并且通過(guò)修改數(shù)據(jù)的方式來(lái)驅(qū)動(dòng)組件的重新渲染。在這個(gè)過(guò)程中,我們不需要去手動(dòng)操作 DOM。

然而在有些場(chǎng)景下,我們還是避免不了要操作 DOM。由于 Vue.js 框架接管了 DOM 元素的創(chuàng)建和更新的過(guò)程,因此它可以在 DOM 元素的生命周期內(nèi)注入用戶的代碼,于是 Vue.js 設(shè)計(jì)并提供了自定義指令,允許用戶進(jìn)行一些底層的 DOM 操作。

舉個(gè)實(shí)際的例子——圖片懶加載。圖片懶加載是一種常見(jiàn)性能優(yōu)化的方式,由于它只去加載可視區(qū)域圖片,能減少很多不必要的請(qǐng)求,極大的提升用戶體驗(yàn)。

而圖片懶加載的實(shí)現(xiàn)原理也非常簡(jiǎn)單,在圖片沒(méi)進(jìn)入可視區(qū)域的時(shí)候,我們只需要讓 img 標(biāo)簽的 src 屬性指向一張默認(rèn)圖片,在它進(jìn)入可視區(qū)后,再替換它的 src 指向真實(shí)圖片地址即可。

如果我們想在 Vue.js 的項(xiàng)目中實(shí)現(xiàn)圖片懶加載,那么用自定義指令就再合適不過(guò)了,那么接下來(lái)就讓我手把手帶你用 Vue3 去實(shí)現(xiàn)一個(gè)圖片懶加載的自定義指令 v-lazy。

插件

為了讓這個(gè)指令方便地給多個(gè)項(xiàng)目使用,我們把它做成一個(gè)插件:

const lazyPlugin = {
? install (app, options) {
? ? app.directive('lazy', {
? ? ? // 指令對(duì)象
? ? })
? }
}

export default lazyPlugin

然后在項(xiàng)目中引用它:

import { createApp } from 'vue'
import App from './App.vue'
import lazyPlugin from 'vue3-lazy'

createApp(App).use(lazyPlugin, {
? // 添加一些配置參數(shù)
})

通常一個(gè) Vue3 的插件會(huì)暴露 install 函數(shù),當(dāng) app 實(shí)例 use 該插件時(shí),就會(huì)執(zhí)行該函數(shù)。在 install 函數(shù)內(nèi)部,通過(guò) app.directive 去注冊(cè)一個(gè)全局指令,這樣就可以在組件中使用它們了。

指令的實(shí)現(xiàn)

接下來(lái)我們要做的就是實(shí)現(xiàn)該指令對(duì)象,一個(gè)指令定義對(duì)象可以提供多個(gè)鉤子函數(shù),比如 mounted、updated、unmounted 等,我們可以在合適的鉤子函數(shù)中編寫(xiě)相應(yīng)的代碼來(lái)實(shí)現(xiàn)需求。

在編寫(xiě)代碼前,我們不妨思考一下實(shí)現(xiàn)圖片懶加載的幾個(gè)關(guān)鍵步驟。

圖片的管理:

管理圖片的 DOM、真實(shí)的 src、預(yù)加載的 url、加載的狀態(tài)以及圖片的加載。

可視區(qū)的判斷:

判斷圖片是否進(jìn)入可視區(qū)域。

關(guān)于圖片的管理,我們?cè)O(shè)計(jì)了 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
? ? }
? })
}

首先,對(duì)于圖片而言,它有三種狀態(tài),加載中、加載完成和加載失敗。

當(dāng) ImageManager 實(shí)例化的時(shí)候,除了初始化一些數(shù)據(jù),還會(huì)把它對(duì)應(yīng)的 img 標(biāo)簽的 src 執(zhí)行加載中的圖片 loading,這就相當(dāng)于默認(rèn)加載的圖片。

當(dāng)執(zhí)行 ImageManager 對(duì)象的 load 方法時(shí),就會(huì)判斷圖片的狀態(tài),如果仍然在加載中,則去加載它的真實(shí) src,這里用到了 loadImage 圖片預(yù)加載技術(shù)實(shí)現(xiàn)去請(qǐng)求 src 圖片,成功后再替換 img 標(biāo)簽的 src,并修改狀態(tài),這樣就完成了圖片真實(shí)地址的加載。

有了圖片管理器,接下來(lái)我們就需要實(shí)現(xiàn)可視區(qū)的判斷以及對(duì)多個(gè)圖片的管理器的管理,設(shè)計(jì) 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)
? ? })
? }
}

這樣每當(dāng)圖片元素綁定 v-lazy 指令,且在 mounted 鉤子函數(shù)執(zhí)行的時(shí)候,就會(huì)執(zhí)行 Lazy 對(duì)象的 add 方法,其中第一個(gè)參數(shù) el 對(duì)應(yīng)的就是圖片對(duì)應(yīng)的 DOM 元素對(duì)象,第二個(gè)參數(shù) binding 就是指令對(duì)象綁定的值,比如:

<img class="avatar" v-lazy="item.pic">

其中 item.pic 對(duì)應(yīng)的就是指令綁定的值,因此通過(guò)binding.value 就可以獲取到圖片的真實(shí)地址。

有了圖片的 DOM 元素對(duì)象以及真實(shí)圖片地址后,就可以根據(jù)它們創(chuàng)建一個(gè)圖片管理器對(duì)象,并添加到 managerQueue 中,同時(shí)對(duì)該圖片 DOM 元素進(jìn)行可視區(qū)的觀察。

而對(duì)于圖片進(jìn)入可視區(qū)的判斷,主要利用了 IntersectionObserver API,它對(duì)應(yīng)的回調(diào)函數(shù)的參數(shù) entries,是 IntersectionObserverEntry 對(duì)象數(shù)組。當(dāng)觀測(cè)的元素可見(jiàn)比例超過(guò)指定閾值時(shí),就會(huì)執(zhí)行該回調(diào)函數(shù),對(duì) entries 進(jìn)行遍歷,拿到每一個(gè) entry,然后判斷 entry.isIntersecting 是否為 true,如果是則說(shuō)明 entry 對(duì)象對(duì)應(yīng)的 DOM 元素進(jìn)入了可視區(qū)。

然后就根據(jù) DOM 元素的比對(duì)從 managerQueue 中找到對(duì)應(yīng)的 manager,并且判斷它對(duì)應(yīng)圖片的加載狀態(tài)。

如果圖片是加載中的狀態(tài),則此時(shí)執(zhí)行manager.load 函數(shù)去完成真實(shí)圖片的加載;如果是已加載狀態(tài),則直接從 managerQueue 中移除其對(duì)應(yīng)的管理器,并且停止對(duì)圖片 DOM 元素的觀察。

目前,我們實(shí)現(xiàn)了圖片元素掛載到頁(yè)面后,延時(shí)加載的一系列處理。不過(guò),當(dāng)元素從頁(yè)面卸載后,也需要執(zhí)行一些清理的操作:

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)
? ? })
? }
}

當(dāng)元素被卸載后,其對(duì)應(yīng)的圖片管理器也會(huì)從 managerQueue 中被移除,并且停止對(duì)圖片 DOM 元素的觀察。

此外,如果動(dòng)態(tài)修改了 v-lazy 指令綁定的值,也就是真實(shí)圖片的請(qǐng)求地址,那么指令內(nèi)部也應(yīng)該做對(duì)應(yīng)的修改:

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)
? ? })
? }
}

至此,我們已經(jīng)實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的圖片懶加載指令,在這個(gè)基礎(chǔ)上,還能做一些優(yōu)化嗎?

指令的優(yōu)化
在實(shí)現(xiàn)圖片的真實(shí) url 的加載過(guò)程中,我們使用了 loadImage 做圖片預(yù)加載,那么顯然對(duì)于相同 url 的多張圖片,預(yù)加載只需要做一次即可。

為了實(shí)現(xiàn)上述需求,我們可以在 Lazy 模塊內(nèi)部創(chuàng)建一個(gè)緩存 cache:

export default class Lazy {
? constructor(options) {
? ? // ...
? ? this.cache = new Set()
? }
}

然后在創(chuàng)建 ImageManager 實(shí)例的時(shí)候,把該緩存?zhèn)魅耄?/strong>

const manager = new ImageManager({
? el,
? src,
? loading: this.loading,
? error: this.error,
? cache: this.cache
})

然后對(duì) 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()
? ? }) ?
? }
}

在每次執(zhí)行 load 前從緩存中判斷是否已存在,然后在執(zhí)行 loadImage 預(yù)加載圖片成功后更新緩存。

通過(guò)這種空間換時(shí)間的手段,就避免了一些重復(fù)的 url 請(qǐng)求,達(dá)到了優(yōu)化性能的目的。

總結(jié):

懶加載圖片指令完整的指令實(shí)現(xiàn),可以在 vue3-lazy 中查看, 在我的課程《Vue3 開(kāi)發(fā)高質(zhì)量音樂(lè) Web app》中也有應(yīng)用。

懶加載圖片指令的核心是應(yīng)用了 IntersectionObserver API 來(lái)判斷圖片是否進(jìn)入可視區(qū),該特性在現(xiàn)代瀏覽器中都支持,但 IE 瀏覽器不支持,此時(shí)可以通過(guò)監(jiān)聽(tīng)圖片可滾動(dòng)父元素的一些事件如 scroll、resize 等,然后通過(guò)一些 DOM 計(jì)算來(lái)判斷圖片元素是否進(jìn)入可視區(qū)。不過(guò) Vue3 已經(jīng)明確不再支持 IE,那么僅僅使用 IntersectionObserver API 就足夠了。

除了懶加載圖片自定義指令中用到的鉤子函數(shù),Vue3 的自定義指令還提供了一些其它的鉤子函數(shù),你未來(lái)在開(kāi)發(fā)自定義指令時(shí),可以去查閱它的文檔,在適合的鉤子函數(shù)去編寫(xiě)相應(yīng)的代碼邏輯。

相關(guān)鏈接:

[1] IntersectionObserver: https://developer.mozilla.org/zh-CN/docs/Web/API/IntersectionObserver

[2] vue3-lazy: https://github.com/ustbhuangyi/vue3-lazy

[3] 《Vue3 開(kāi)發(fā)高質(zhì)量音樂(lè) Web app》:https://coding.imooc.com/class/503.html

[4] Vue3 自定義指令文檔: https://v3.cn.vuejs.org/guide/custom-directive.html

相關(guān)文章

  • vue使用Echarts繪制地圖完整步驟

    vue使用Echarts繪制地圖完整步驟

    這篇文章主要給大家介紹了關(guān)于vue使用Echarts繪制地圖的相關(guān)資料,Apache ECharts一個(gè)基于JavaScript的開(kāi)源可視化圖表庫(kù),提供了常規(guī)的折線圖、柱狀圖、散點(diǎn)圖、餅圖、K線圖,用于統(tǒng)計(jì)的盒形圖,需要的朋友可以參考下
    2023-09-09
  • 如何抽象一個(gè)Vue公共組件

    如何抽象一個(gè)Vue公共組件

    這篇文章主要介紹了如何抽象一個(gè)Vue公共組件,以一個(gè)數(shù)字鍵盤(pán)組件為例,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-10-10
  • vue 路由嵌套高亮問(wèn)題的解決方法

    vue 路由嵌套高亮問(wèn)題的解決方法

    這篇文章主要介紹了vue 路由嵌套高亮問(wèn)題的解決方法,主路由通過(guò)v-for循環(huán)出來(lái),次路由通過(guò)url拼接的方式導(dǎo)航到子路由頁(yè)面,具體實(shí)現(xiàn)代碼大家參考下本文
    2018-05-05
  • Vue事件處理原理及過(guò)程詳解

    Vue事件處理原理及過(guò)程詳解

    這篇文章主要介紹了vue事件處理原理及過(guò)程詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-03-03
  • vue3實(shí)現(xiàn)監(jiān)聽(tīng)store中state狀態(tài)變化的簡(jiǎn)單方法

    vue3實(shí)現(xiàn)監(jiān)聽(tīng)store中state狀態(tài)變化的簡(jiǎn)單方法

    這篇文章主要給大家介紹了關(guān)于vue3實(shí)現(xiàn)監(jiān)聽(tīng)store中state狀態(tài)變化的簡(jiǎn)單方法,store是一個(gè)狀態(tài)管理工具,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2023-10-10
  • Vue.js系列之項(xiàng)目搭建(1)

    Vue.js系列之項(xiàng)目搭建(1)

    今天要講講Vue2.0了。最近將公司App3.0用vue2.0構(gòu)建了一個(gè)web版,因?yàn)槭堑谝淮问褂胿ue,而且一開(kāi)始使用的時(shí)候2.0出來(lái)一個(gè)月不到,很多坑都是自己去踩的,現(xiàn)在項(xiàng)目要上線了,所以記錄一些過(guò)程
    2017-01-01
  • vue實(shí)現(xiàn)圖片裁剪后上傳

    vue實(shí)現(xiàn)圖片裁剪后上傳

    這篇文章主要為大家詳細(xì)介紹了vue實(shí)現(xiàn)圖片裁剪后上傳,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2020-12-12
  • Vue3中使用i18n,this.$t報(bào)錯(cuò)問(wèn)題及解決

    Vue3中使用i18n,this.$t報(bào)錯(cuò)問(wèn)題及解決

    這篇文章主要介紹了Vue3中使用i18n,this.$t報(bào)錯(cuò)問(wèn)題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-04-04
  • 詳解Vue.js中的組件傳值機(jī)制

    詳解Vue.js中的組件傳值機(jī)制

    Vue.js 是一款流行的前端框架,它提供了一些方便的機(jī)制來(lái)管理組件之間的通信,其中包括組件傳值,本文將詳細(xì)介紹 Vue.js 中的組件傳值機(jī)制,包括父子組件傳值、兄弟組件傳值、跨級(jí)組件傳值等多種方式,需要的朋友可以參考下
    2023-08-08
  • 解決vue-router 嵌套路由沒(méi)反應(yīng)的問(wèn)題

    解決vue-router 嵌套路由沒(méi)反應(yīng)的問(wèn)題

    這篇文章主要介紹了解決vue-router 嵌套路由沒(méi)反應(yīng)的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2020-09-09

最新評(píng)論