因为要在 vue 里面使用,所以做个插件可以方便很多。
项目需求
- 第一次访问,初始化,需要建立表、导入初始数据
- 组件获取需要的数据
- 数据更新的问题
插件功能
- 在 main 里面安装插件。
- 建立一个 help, 用 provide 保存,以便于其他组件使用。
定义结构
我们需要先定义一下结构:
{
dbConfig: db, // 连接字符串,库名和版本
stores: { // 数据库里的表的集合
moduleMeta: { // 对象仓库名称
id: 'moduleId', // keyPath 的名称
index: {}, // 设置所以
isClear: false // 是否清空数据
}
},
init (help) {
// 导入初始数据
}
dbConfig
设置库名称和版本号stores
对象仓库的集合,每个属性就是一个对象仓库,依据属性创建对象仓库。init
传递一个help过来,可以添加数据。
实现插件
import { inject } from 'vue'
import IndexedDB from './help.js'
export default {
_indexedDBFlag: Symbol('nf-indexedDB-help'),
_help: {}, // 访问数据库的实例
/**
* 根据参数创建一个数据库的实例,初始化数据库
* * 删表、建表、添加默认数据
* @param {*} info 参数
* @returns 安装一个Vue的插件
* * dbFlag: '数据库标识',
* * dbConfig: { // 连接数据库
* * * dbName: 'vite2-blog',
* * * ver: 1.0
* * },
* * init: () => {},
* * stores: {
* * * storeName: { // 表名
* * * * id: 'id', // 主键名称
* * * * index: {
* * * * * name: ture, // 索引:是否可以重复
* * * * },
* * * * isDeleteOldTable: false, // 是否删除之前的表
* * * }
* * }
*/
createHelp (info) {
let indexedDBFlag = this._indexedDBFlag
if (typeof info.dbFlag === 'string') {
indexedDBFlag = Symbol.for(info.dbFlag)
}
if (typeof info.dbFlag === 'symbol') {
indexedDBFlag = info.dbFlag
}
// 连接数据库
const help = new IndexedDB(info)
return {
// 安装插件
install (app, options) {
// 注入状态,用 symbol 作为标记,避免重名
app.provide(indexedDBFlag, help)
}
}
},
// 代码里面调用
useHelp (_dbFlag) {
let flag = this._indexedDBFlag
if (typeof _dbFlag === 'string') {
flag = Symbol.for(_dbFlag)
}
if (typeof _dbFlag === 'symbol') {
flag = _dbFlag
}
if (typeof inject(flag) === 'undefined') {
// 根级调用
return this._help
} else {
// 子组件调用
const help = inject(flag)
return help
}
}
}
_indexedDBFlag
Symbol 类型的标识,区分不同的数据库。createHelp
创建一个 vue 的插件。这里基本没啥代码,就是做个help,然后用 provide 注入一下以便于其他组件也可以使用。
主要代码还是在 help 的 onupgradeneeded 实现的,这个我也没想到更好的办法。useHelp
子组件里面获取 help,然后使用这个 help 就可以对 indexedDB 进行操作了。
为啥要弄个 help ?因为想更好的控制初始化和组件读取数据的先后关系。
建库与升级
因为 indexedDB 在初始化的时候会触发 onupgradeneeded ,所以一些代码还是要写在这里。
onupgradeneeded
// 根据配置信息建立表
this.dbRequest.onupgradeneeded = (event) => {
this._dataState = 'loading' // 需要加载
const db = event.target.result
console.log('【2】升级数据库 onupgradeneeded --- ', db)
for (const key in info.stores) {
const store = info.stores[key]
// 验证有没有表,没有的话建立一个对象表
if (!db.objectStoreNames.contains(key)) {
// const objectStore = db.createObjectStore(object.objectStoreName, { keyPath: 'id' }) /* 创建person仓库(表) 主键 */
const objectStore = db.createObjectStore(key, { keyPath: store.id }) /* 自动创建主键 autoIncrement: true */
// 建立索引
for (const key2 in store.index) {
const unique = store.index[key2]
objectStore.createIndex(key2, key2, { unique: unique })
}
// if (config.debug) {
console.log('onupgradeneeded - 建立了一个新的对象仓库:', key)
// }
}
}
}
// 根据配置信息建立表
this.dbRequest.onerror = (event) => {
// 出错
console.log('打开数据库出错:', event.target.error)
}
}
一开始想在这里添加初始数据的,但是实验了一下发现不行,因为这时候还是“建库”状态,不让填数据。
所以只好把填数据的操作放在 onsuccess 里面。
onsuccess
// 打开成功,记录连接对象
this.dbRequest.onsuccess = (event) => {
this._db = event.target.result
if (this._dataState === 'none') return
if (this._dataState === 'loading') {
// 升级后需要添加初始数据
if (typeof info.init !== 'function') {
this._dataState = 'none'
// 没有设置添加初始化数据的函数
return
}
if (typeof info.init === 'function') {
// 调用填数据的函数,传入help
info.init(this)
}
}
}
beginInit
然后我又偷偷的做了一个专门的事务。
// 初始化填数据的事务
beginInit (storeName) {
return new Promise((resolve, reject) => {
//
const tranRequest = this._db.transaction(storeName, 'readwrite')
const store = tranRequest.objectStore(storeName) // 获取store
tranRequest.onerror = (event) => {
console.log('读写事务出错:', event.target.error)
reject('读写事务出错:' + event.target.error)
tranRequest.abort()
}
tranRequest.oncomplete = (event) => {
// 事务执行完毕
this._dataState = 'none'
}
// 返回对象仓库
resolve(store)
})
}
开启一个读写事务,然后打开对象仓库,把仓库返回去。然后调用者就可以用这个仓库添加对象了。
然后在 oncomplete 里面设置状态。这样就都联系起来了。
好吧有点绕。很容易晕。