Разве не было бы неплохо, если бы параметры окна браузера были реактивными, и вы могли бы делать такие вещи, как просмотр значений window.scrollY или window.innerWidth во всем приложении? Такие параметры, как (но не ограничиваясь ими):

window.scrollX
window.scrollY
window.innerHeight
window.innerWidth

На данный момент в VueJS нет возможности «наблюдать» за свойством окна или делать его реактивным. Я не мог найти способ сделать scrollY реактивным во многих местах своего приложения эффективно. Я также не хотел добавлять шаблон, подобный приведенному ниже, в каждый из моих однофайловых компонентов Vue:

created () {
  this.$el.addEventListener('click', this.someMethod)
},
destroyed () {
  this.$el.removeEventListener('click', () => this.someMethod)
}

Выполнение этого в нескольких компонентах действительно заставляет меня чувствовать себя плохо из-за того, что приходится снова и снова выполнять такую ​​громоздкую операцию. Это также неэффективно и открывает для меня возможность ошибки пользователя, если я забыл добавить destroyed() жизненный цикл.

Почему Vuex не подходит для этого варианта использования?

Магазин Vuex - безусловно, один из моих любимых инструментов в экосистеме Vue. Однако в этом случае он не подходит для управления оконным интерфейсом. Причина в том, что очистка.

Я хочу, чтобы Vuex управлял только «состоянием приложения», и window.scrollY определенно не попадает в категорию состояния приложения. Кроме того, я пытаюсь сделать так, чтобы мои инструменты разработки Vue не выглядели так при каждом запуске события прокрутки.

Это полностью избавляет от Vuex. А теперь давайте исследуем дальше.

Прежде чем мы продолжим, спасибо Томасу Финдли за исправление меня по поводу использования термина EventBus. EventBus - это подшаблон pub, используемый для взаимодействия компонентов друг с другом через разделенный экземпляр Vue. Часто не рекомендуется использовать магазин Vuex (архитектура потока).

Управление состоянием окна с помощью сопоставления экземпляров

Vue может создать еще один действительно независимый и реактивный экземпляр Vue, который отделен от основного экземпляра. Мы часто делаем это при запуске нового приложения Vue или создании EventBus. Вот как мы это делаем:

const WindowInstanceMap = new Vue()

Главное, чего мы здесь хотим добиться, - это реактивность. Например, если scrollY изменяется, я хочу, чтобы это вызвало некоторые изменения в вычисляемых методах, расположенных в моих компонентах приложения.

Имея это в виду, давайте создадим WindowInstanceMap. Создайте реактивные scrollY данные и прослушиватель событий, который незаметно «изменяет» их.

Таким образом, мы создали клон оконного интерфейса, и scrollY является реактивным. А теперь попробуем где-нибудь его использовать.

Давайте сначала импортируем это в App.js, чтобы «инициализировать» компонент:

// App.js
import WindowInstanceMap from './WindowInstanceMap.js'

Это запустит create() жизненный цикл WindowInstanceMap и зарегистрирует прослушиватель событий прокрутки в окне.

Теперь по моим компонентам. Все, что мне нужно сделать, это сопоставить его с вычисляемой опорой.

// AppNav.vue
import WindowInstanceMap from './WindowInstanceMap.js'
export default {
  computed: {
    scrollY () { return WindowInstanceMap.scrollY }
  }
}

Это похоже на использование mapState, представленное Vuex. Здесь мы отображаем вычисленное значение this.scrollY на WindowInstanceMap.scrollY.

Теперь он является реактивным, и мы можем использовать его в других вычисляемых свойствах, таких как:

// AppNav.vue
import WindowInstanceMap from './WindowInstanceMap.js'
export default {
  computed: {
    scrollY () { return WindowInstanceMap.scrollY },
    isCollapsed () {
      return this.scrollY < 100
    }
  }
}

При такой настройке у меня есть только ОДИН прослушиватель событий в WindowInstanceMap. Все мои компоненты безупречно чистые. Я могу даже делать сложные вещи, связанные с оконным интерфейсом в WindowInstanceMap; и они будут реактивными на протяжении всего приложения.

Самое лучшее в этом то, что он не засыпает мои инструменты разработки ненужными мутациями. Лучшее из обоих миров ИМО.

Единственный недостаток, который я вижу в этой настройке, - это то, что dev-tools не поддерживает несколько экземпляров Vue. Так что отладить WindowInstanceMap.js будет сложнее. Я хотел бы сделать так, чтобы WindowInstanceMap был простым и понятным компонентом.

Я использую это как в своих приложениях Vue, так и в Nuxt, и они мне очень пригодились.

Вот как вы его используете в NuxtJS

Давайте создадим WindowInstanceMap.js в папке «plugins».

// WindowInstanceMap.js
import Vue from 'vue'
const WindowInstanceMap = new Vue({
  data() {
    return {
      scrollY: 0
    }
  },
created() {
    window.addEventListener('scroll', e => {
      // Debounce this
      window.requestAnimationFrame(() => {
        this.scrollY = window.scrollY
      })
    })
  }
})
// Inject plugin as Vue.prototype.$window
export default (context, inject) => {
  inject('window', EventBus)
}

Теперь давайте импортируем его в наш nuxt.config.js. Не забудьте отключить рендеринг на стороне сервера с помощью ssr: false, потому что он будет работать только в браузере (когда окно доступно).

// nuxt.config.js
plugins: [
  { src: '~/plugins/WindowInstanceMap', ssr: false }
]

Это оно!

Спасибо за прочтение. Я надеюсь, что принес пользу и помог вам. Хотелось бы услышать, что вы думаете об этом подходе, и были бы рады услышать ваши предложения о том, как его улучшить.

Если вам нравится контент, поделитесь им со мной в Twitter и Instagram.





📝 Прочтите этот рассказ позже в Журнале.

🗞 Просыпайтесь каждое воскресенье утром и слышите самые интересные истории, мнения и новости недели, ожидающие вас в почтовом ящике: Получите примечательный информационный бюллетень›