Практика с вами, чтобы написать пользовательскую директиву Vue3

внешний интерфейс JavaScript Vue.js
Практика с вами, чтобы написать пользовательскую директиву Vue3

задний план

Как мы все знаем, основной идеей Vue.js является управление данными + компонентизация.Обычно процесс разработки страницы заключается в написании некоторых компонентов и изменении данных для управления повторным рендерингом компонентов. В этом процессе нам не нужно вручную манипулировать DOM.

Однако в некоторых сценариях мы все же не можем избежать манипуляций с DOM. Поскольку платформа Vue.js берет на себя процесс создания и обновления элементов DOM, она может внедрять пользовательский код в течение жизненного цикла элементов DOM, поэтому Vue.js разрабатывает и предоставляет настраиваемые директивы, которые позволяют пользователям выполнять некоторые базовые операции с DOM.

Чтобы дать фактический пример - картинка ленивая загрузка. Картинки Lazy Loading - это общая оптимизация производительности, поскольку это всего лишь видимая область изображения для загрузки, вы можете уменьшить много ненужных запросов, значительно улучшит опыт пользователя.

Принцип реализации ленивой загрузки изображения также очень прост: когда изображение не попадает в видимую область, нам нужно только позволить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-элемента изображения.

Для суждения о картине, попадающей в видимую область, в основном используютIntersectionObserverAPI, параметры соответствующей функции обратного вызова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-lazyпосмотреть в, в моем курсе«Vue3 разрабатывает высококачественное музыкальное веб-приложение»Также есть приложения в .

Суть инструкции отложенной загрузки изображения заключается в примененииIntersectionObserverAPI, чтобы определить, является ли изображение в область просмотра, какие функции поддерживаются в современных браузерах, но IE браузер не поддерживает в это время, некоторые из событий могут быть прокручены путем прослушивания родительского элемента, такого как изображенияscroll,resizeПодождите, а затем используйте некоторые вычисления DOM, чтобы определить, входит ли элемент изображения в видимую область. Однако Vue3 явно больше не поддерживает IE, поэтому просто используйтеIntersectionObserverAPI достаточно.

В дополнение к функциям-ловушкам, используемым в пользовательских инструкциях отложенной загрузки изображений, пользовательские инструкции Vue3 также предоставляют некоторые другие функции-ловушки.Когда вы будете разрабатывать пользовательские инструкции в будущем, вы можете обратиться к егоДокументация, в соответствующей функции ловушки, чтобы написать соответствующую логику кода.