Использование IndexedDB с Vue.js
После статьи описывающие базовые принципы использования API — IndexedDB самое время написать статью о использования IndexedDB в приложение на Vue.js. Главным образом потому, что, как ни крути, в последнее время не так уж много статей об этом.
Спонсор поста
Ремонт ноутбуков Алматы
Сервис центр Disketa disketa-service.kz
Оперативный ремонт и обслуживание ноутбуков:
— Цены от 2200 тг.
— Бесплатная консультация и диагностика.
— Опытные мастера со стажем более 7 лет.
— Оплата только после ремонта.
— Предоставляем гарантию от 1 года!
И так начнем с создания нового Vue приложения через CLI.
vue create indexdb
Сами выберите опции которые сочтете наиболее подходящими. В тестовом приложение мы будем использовать только store. Его можно выбрать среди опции либо позже подключить самостоятельно.
Я выбрал следующиее опции.
Vue CLI v4.1.1 ┌─────────────────────────────────────────┐ │ │ │ New version available 4.1.1 → 4.1.2 │ │ Run npm i -g @vue/cli to update! │ │ │ └─────────────────────────────────────────┘ ? Please pick a preset: Manually select features ? Check the features needed for your project: ◉ Babel ◯ TypeScript ◉ Progressive Web App (PWA) Support ◯ Router ❯◉ Vuex ◯ CSS Pre-processors ◉ Linter / Formatter ◯ Unit Testing ◯ E2E Testing
? Please pick a preset: Manually select features ? Check the features needed for your project: Babel, PWA, Vuex, Linter ? Pick a linter / formatter config: ESLint with error prevention only ESLint + Airbnb config ❯ ESLint + Standard config ESLint + Prettier
$ cd indexdb
После создания нового приложения удалим все лишние из него. Удалим все лишнее из App.vue
<template> <div id="app"> </div> </template> <script> export default { name: 'app' } </script>
И удалим компонент HelloWorld.vue (если он был создан CLI)
В нашем тестовом приложение мы будем использовать простейшую базу книг в которой будем хранить название книги и ее цену. У нас будет одна кнопка для создания новой записи и возле каждой записи кнопка для ее удаления. Книги будут отображаться в виде списка следующим образом.
Далее создадим новый компонент Books для отображения книг. Для этого создадим новый файл components/books.vue со следующим содержимым:
<template> <div class="books"> </div> </template> <script> export default { name: 'Books', data () { return { } }, computed: { }, created () { }, methods: { } } </script> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> h3 { margin: 40px 0 0; } ul { list-style-type: none; padding: 0; } li { margin: 0 10px; } a { color: #42b983; } </style>
Подключим этот новый компонент в файле App.vue. Для этого внесем в App.vue следующие изменения:
<template> <div id="app"> <Books /> </div> </template> <script> import Books from './components/Books.vue' export default { name: 'app', components: { Books } } </script>
Далее поправим наше хранилище. В каталоге проекта должен быть создан каталог store, в нем должен находится файл index.js со следующим содержимым:
import Vue from 'vue' import Vuex from 'vuex' import state from '@/store/state' import mutations from '@/store/mutations' import actions from '@/store/actions' Vue.use(Vuex) export default new Vuex.Store({ state, mutations, actions })
Так же в этом каталоге нужно создать 3 файла state.js, actions.js, mutations.js Структура каталога store должна быть следующей:
... store/ index.js state.js actions.js mutations.js ....
В файле state.js будет храниться наши переменные состояния хранилища. Мы будет там хранить только список книг, поэтому содержимое state.js будет следующим:
export default { books: [] }
В файле mutations.js будут храниться базовые операции над этим списком книг, поэтому его содержимое будет следующее:
export default { SET_BOOKS (state, books) { state.books = books } }
Файл actions.js, пока пусть будет пустым, мы вернемся к нему чуть позже.
Далее создадим самое важное для этого тестового приложения. Создадим файл с API для работы с IndexedDB. Для создадим в папке src новым каталог api и в нем создадим новый файл idb.js, со следующим содержимым:
const DB_NAME = 'bookdb' const STORAGE_NAME = 'books' const DB_VERSION = 1 let DB export default { async getDb () { return new Promise((resolve, reject) => { if (DB) { return resolve(DB) } const request = window.indexedDB.open(DB_NAME, DB_VERSION) request.onerror = e => { console.log('Error opening db', e) // eslint-disable-next-line prefer-promise-reject-errors reject('Error') } request.onsuccess = e => { DB = e.target.result resolve(DB) } request.onupgradeneeded = e => { let db = e.target.result db.createObjectStore(STORAGE_NAME, { autoIncrement: true, keyPath: 'id' }) } }) }, async deleteBook (book) { const db = await this.getDb() return new Promise(resolve => { const trans = db.transaction([STORAGE_NAME], 'readwrite') trans.oncomplete = () => { resolve() } const store = trans.objectStore(STORAGE_NAME) store.delete(book.id) }) }, async getBooks () { let db = await this.getDb() return new Promise(resolve => { let trans = db.transaction([STORAGE_NAME], 'readonly') trans.oncomplete = () => { resolve(books) } const store = trans.objectStore(STORAGE_NAME) const books = [] store.openCursor().onsuccess = e => { const cursor = e.target.result if (cursor) { books.push(cursor.value) cursor.continue() } } }) }, async saveBook (book) { let db = await this.getDb() return new Promise(resolve => { let trans = db.transaction([STORAGE_NAME], 'readwrite') trans.oncomplete = () => { resolve() } let store = trans.objectStore(STORAGE_NAME) store.put(book) }) } }
В этом файле у нас реализованы один внутренний метод getDb который используется только внутри API и три внешних метода: getBooks для получения списка книг из БД, saveBook для создания новой книги и deleteBook для удаления выбранной книги. Более подробно как работают эти методы вы можете почитать в моей предыдущей статье.
Далее задействуем эти методы в файле хранилища store/actions.js:
import api from '@/api/idb' export default { addBookToDb ({ commit }, { book }) { return api.saveBook(book) }, async getBooks ({ commit }) { let books = await api.getBooks() commit('SET_BOOKS', books) }, deleteBookFromDb ({ commit }, { book }) { return api.deleteBook(book) } }
Тут мы описали три действия (action): получения списка книг, добавления новой книги и удаление выбранной книги.
Теперь все готово что бы описать весь нужный функционал в компоненте Books.vue
<template> <div class="books"> <button @click="addBook" :disabled="addDisabled">Add Book</button> <ul> <li v-for="book in books" :key="book.id"> {{book.name}} with price: {{book.price}}. <button @click="deleteBook(book)">Delete</button> </li> </ul> </div> </template> <script> import { mapActions } from 'vuex' export default { name: 'Books', data () { return { addDisabled: false } }, computed: { books () { return this.$store.state.books } }, created () { this.getBooks() }, methods: { ...mapActions(['getBooks', 'addBookToDb']), async addBook () { this.addDisabled = true // random book for now const book = { name: 'Book_' + Math.floor(Math.random() * 100), price: Math.floor(Math.random() * 10) + 1 } console.log('about to add ' + JSON.stringify(book)) await this.addBookToDb({ book }) this.getBooks() this.addDisabled = false }, async deleteBook (book) { await this.$store.dispatch('deleteBookFromDb', { book }) this.getBooks() } } } </script>
Тут все очень просто. В шаблоне мы добавили кнопку создания новой книги и вывели список книг. В каждой строке списка вы выводим название книги ее цену и кнопку удаления данной книги. Список книг books мы формируем прямо из хранилища через computed свойство. В момент создания компонента в методе created мы погружаем список книг в хранилище через action this.getBooks. И у нас еще два метода addBook для создания новой книги и deleteBook для удаления выбранной книги.
На этом все наше тестовое приложение готов. Теперь достаточно запустить команду CLI
npm run serve
И все должно работать.
Заключение
В целом мы рассмотрели очень простой и понятный пример использования IndexedDB во Vue.js. Надеюсь, если у вас возникла необходимость использования встроенной в браузере БД этот пример облегчит вам понимание того как можно использовать IndexedDB в Vue.js. Если вы хотите узнать больше, обязательно посмотрите MDN документацию по этому вопросу. Как всегда, оставляйте комментарий ниже, если у вас есть вопрос или предложение!
Мы менеджим в проекте данные между вкладками браузера с помощью IndexedDB. Данные развесистые и большие localstorage не хватает.
Используем в проекте Dexie (https://dexie.org). Пакет сам по себе мелкий, но удобно.
Спасибо за наводку
А как насчет обновления данных о книге? Dexie вроде предоставляет эту возможность