Для тех, кто незнаком с этой библиотекой, советую попробовать, так она может де-факто стать стандартом для использования в проектах на Vue 3, как, например, в свое время была библиотека lodash для почти любых проектов на js.

Остальные наверное уже успели заценить весь обширный функционал, который она предоставляет. Некоторые уже использовали ее на Vue 2, но далеко не все новые функции поддерживают старую версию. Арсенал библиотеки впечатляет, тут и простые утилиты вроде клика вне элемента, и различные интеграции с Firebase, Axios, Cookies, QR, локальным хранилищем, браузером, RxJS, анимации, геолокации, расширения для стандартных Vue-хуков, медиа-плеер и многое другое. Среди спонсоров отмечен сам Эван Ю, что как бы намекает. Библиотека регулярно получает обновления, баги закрываются, а сообщество растет. В общем у нее есть все для успеха.

В этой статье я расскажу только о 3 фичах, но, безусловно, и все остальные нуждаются во внимании.

onClickOutside —  клики вне элемента

С установкой библиотеки вы справитесь, я уверен, поэтому перейдем сразу к интересным вещам. Для разогрева рассмотрим простой хук, который отслеживает клики вне заданного элемента —  onClickOutside. Существует множество пакетов, которые предоставляют данный функционал, не считая бесчисленные велосипеды, которые писал наверное каждый. Обычно его реализуют через добавление пользовательской Vue-директивы к нужному элементу, например v-clickOutside, а вот использование хука необычно.

Я использовал этот хук в своей тудушке, в компоненте ToDoItem.vue:

<template>
    <li ref="todoItem">
      <input type="checkbox" />
      <span
        v-if="!editable"
        @click="editable = !editable"
      >
        {{ todo.text ? todo.text : "Click to edit Todo" }}
      </span>
      <input
        v-else
        type="text"
        :value="todo.text"
        @keyup.enter="editable = !editable"
      />
    </li>
</template>

<script lang="ts">
  import { defineComponent, PropType, ref } from "vue"
  import ToDo from "@/models/ToDoModel"
  import { onClickOutside } from "@vueuse/core"

  export default defineComponent({
    name: "TodoItem",
    props: {
      todo: {
        type: Object as PropType<ToDo>,
        required: true
      }
    },
    setup() {
      const todoItem = ref(null)
      const editable = ref(false)

      onClickOutside(todoItem, () => {
        editable.value = false
      })

      return { todoItem, editable }
    }
  })
</script>

Я удалил лишний код, чтобы не отвлекал, но все еще компонент достаточно большой. Обратите внимание на код, который находится внутри хука setup, сначала мы создаем пустую ссылку todoItem, которую вешаем на нужный элемент в шаблоне, а потом передаем первым параметром в хук onClickOutside, а вторым параметром коллбэк с нужными нам действиями. При клике на тег span, он заменится на тег input, а если кликнуть вне тега li с атрибутом ref="todoItem", то input сменится тегом span.

useStorage —  реактивное локальное хранилище

Следующая функция, о которой я расскажу,  —  это useStorage. Эта функция создает реактивное локальное хранилище. Это очень удобно, так как никаких дополнительные манипуляции с хранилищем больше не потребуются. Данные будут сохранятся, обновляться и удаляться автоматически. Я применил эту функцию в Vuex для хранения записей со списком дел.

// @/store/index.ts
import { createStore } from 'vuex'
import Note from '@/models/NoteModel'
import { useStorage } from '@vueuse/core'

const localStorageNotes: unknown = useStorage('my-notes', [] as Note[])

export default createStore({
  state: {
    notes: localStorageNotes as Note[]
  },
  mutations: {              
    addNote(state) {
      state.notes.push(note)
    },
   // mutations
  },
  actions: {
    // actions
  },
  getters: {
    // getters
  },
  strict: true

})

Первым параметром функция useStorage принимает ключ, под которым она будет сохранять ваши данные, а вторым их начальное значение. Полученную переменную localStorageNotes передаем в state.notes. Массив notes можно использовать как обычно, например добавлять новую запись в мутациях. Теперь записи будут сохраняться даже после перегрузки страницы и самого браузера.

При написании этого кода TypeScript начал ругаться на отсутствие типа у переменной localStorageNotes. Этого следовало ожидать, так как эта переменная создается с помощью ref и не создана для использования вне хука setup. Я не нашел другого решения, кроме как присвоить ей значение any или unknown. Код работает, но выглядит не очень. Если кто знает лучшее решение, подскажите в комментариях. Будем надеяться, что авторы библиотеки создадут лучшую интеграцию с Vuex, где эта функциональность так необходима.

Для сравнения также полезно ознакомиться с примером использования useStorage от авторов. Разница в том, что в setup работать с реактивным хранилищем нужно не напрямую, а через его свойство value. В html-шаблоне же, все как обычно.\

useRefHistory —  история изменений

useRefHistory —  хук который позволит записывать историю изменений данных и предоставляет undo/redo функциональность. Я использовал ее для создания кнопок Undo и Redo на странице создания и редактирования записи со списком дел. Так как переменная currentNote, которая отвечает за хранение редактируемой записи, тоже находится во Vuex-хранилище. Я так же использовал ее именно там и так же получил ошибку типизации. Рассмотрим код получше:

import { createStore } from 'vuex'
import Note from '@/models/NoteModel'
import ToDo from "@/models/ToDoModel"
import { useRefHistory } from '@vueuse/core'
import { ref } from 'vue'

const note: any = ref({
  title: "",
  todos: [] as ToDo[]
})
const { history, undo, redo, canUndo, canRedo, clear } = useRefHistory(note, {
  deep: true
})

export default createStore({
  state: {
    currentNote: note as Note,
    currentId: 0
  },
  mutations: {
    // mutations
    clearHistory() {
      clear()
    },
    undoChanges() {
      undo()
    },
    redoChanges() {
      redo()
    }
  },
  actions: {
    

  },
  getters: {
    canUndo() {
      return canUndo.value
    },
    canRedo() {
      return canRedo.value
    }
  },
  strict: true

})

Создаем реактивную переменную с помощью ref, передаем ее в хук useRefHistory, в параметрах хука обозначаем deep: true, для вложенных объектов. С помощью деструктурирующего присваивания из useRefHistory получаем history, undo, redo, canUndo,canRedo и clear. Функции undo и redo, необходимо применять только в мутациях, чтобы Vuex не ругался. Свойства canUndo и canRedo можно передать через геттеры, которые потом повесить на атрибуты disabled в кнопках. clear — необходима для очистки истории после окончания редактирования записей. Хук useManualRefHistory делает практически тоже самое, но сохранение в историю происходит только по вызову команды commit().


Я рассказал всего про 3 функции из большого арсенала инструментов VueUse для разработки на Vue 3. Для более глубокого изучения советую посетить сайт этой замечательной библиотеки. Документация все еще далека от совершенства, но она регулярно обновляется как и сама библиотека.

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