Skip to content

起步

IndexDB 是一个运行在浏览器上的非关系型数据库。理论上是没有存储上限的一般来说不会小于 250M)。它不仅可以存储字符串,还可以存储二进制数据。

特点:

  • 键值对储存 所有类型的数据都可以直接存入,包括 JavaScript 对象。对象仓库中,数据以"键值对"的形式保存,每一个数据记录都有对应的主键,主键是独一无二的,不能有重复,否则会抛出一个错误。

  • 异步 IndexedDB 操作时不会锁死浏览器,用户依然可以进行其他操作,这与 LocalStorage 形成对比,后者的操作是同步的。异步设计是为了防止大量数据的读写,拖慢网页的表现。

  • 同源限制 每一个数据库对应创建它的域名。网页只能访问自身域名下的数据库,而不能访问跨域的数据库。

  • 支持二进制储存

  • 储存空间大

keyPath:In-line vs Out-of-line

如果用作 keyPath 的值跟数据没有关系,那么就可以不设置 keyPath,而是选择在 put 的时候再更新

特性In-line Key (设置了 keyPath)Out-of-line Key (未设置 keyPath)
创建方式{ keyPath: null } (默认)
Key 的来源从数据对象内部提取(如 obj.id)必须在 put 的第二个参数手动传入
put 调用store.put(obj)store.put(obj, key)
手动传 Key禁止如果传了第二个参数会报错,必须。如果不传且没设自增则会报错

transaction 事务

第一个参数接受数组,表示这次操作所涉及的仓库,则之后的操作,会同时成功或失败;如果你需要同时更新两个不同的 Store(比如“余额表”和“账单表”),为了防止扣了钱但没记账的情况发生,你必须把它们放在同一个事务里。

ts
// 同时锁定两个 Store
const transaction = db.transaction(['accounts', 'logs'], 'readwrite')

const accountsStore = transaction.objectStore('accounts')
const logsStore = transaction.objectStore('logs')

// 只有这两个操作都成功,事务才会提交
accountsStore.put({ id: 1, balance: 100 })
logsStore.add({ time: Date.now(), msg: 'Update balance' })

使用

打开、创建数据库
ts
/**
 *  indexedDB.open(databaseName, version)
 *  这个方法接受两个参数,第一个参数是字符串,表示数据库的名字。如果指定的数据库不存在,就会新建数据库。第二个参数是整数,表示数据库的版本。如果省略,打开已有数据库时,默认为当前版本;新建数据库时,默认为1。
 *  如果指定的版本号,大于数据库的实际版本号,就会发生数据库升级事件upgradeneeded
 *  主键跟索引比较:
 *  - 主键:类似身份证,唯一性,以此才能获取到对应的数据并进行更改(get()、IDBKeyRange)
 *  - 索引:分类,非唯一性,更好的查找对应类型的数据(index('name'))
 *  */
const request = indexedDB.open(dbName)

request.onsuccess = () => {
    console.log(request.result)
}

request.onerror = reject
/**
 * 在这里创建对应的数据库结构;
 * 先判断是否存在 `person` 库,否则创建
 * 主键(key)是默认建立索引的属性。比如,数据记录是{ id: 1, name: '张三' },那么id属性可以作为主键。主键也可以指定为下一层对象的属性,比如{ foo: { bar: 'baz' } }的foo.bar也可以指定为主键。
 */

request.onupgradeneeded = function (event) {
    db = event.target.result
    if (!db.objectStoreNames.contains('person')) {
        const objectStore = db.createObjectStore('person', { keyPath: 'id' })
        // 创建索引
        objectStore.createIndex('name', 'name', { unique: false })
        objectStore.createIndex('email', 'email', { unique: true })
    }
}
新增数据
ts
var request = db
    .transaction(['person'], 'readwrite')
    .objectStore('person')
    .add({ id: 1, name: '张三', age: 24, email: 'zhangsan@example.com' })

request.onsuccess = function (event) {
    console.log('数据写入成功')
}

request.onerror = function (event) {
    console.log('数据写入失败')
}
读取数据
ts
/**
 * objectStore.get()方法用于读取数据,参数是主键的值。
 * 如果只是想获取指定数量数据,可以使用 `objectStore.getAll(null, count)`
 */
var transaction = db.transaction(['person'])
var objectStore = transaction.objectStore('person')
var request = objectStore.get(1)

request.onerror = function (event) {
    console.log('事务失败')
}

request.onsuccess = function (event) {
    if (request.result) {
        console.log('Name: ' + request.result.name)
        console.log('Age: ' + request.result.age)
        console.log('Email: ' + request.result.email)
    } else {
        console.log('未获得数据记录')
    }
}
更新数据
ts
/**
 * 因为这里使用了 id 作为主键,因此会自动更新 id=1 的数据
 */
var request = db
    .transaction(['person'], 'readwrite')
    .objectStore('person')
    .put({ id: 1, name: '李四', age: 35, email: 'lisi@example.com' })

request.onsuccess = function (event) {
    console.log('数据更新成功')
}

request.onerror = function (event) {
    console.log('数据更新失败')
}
删除数据
ts
var request = db.transaction(['person'], 'readwrite').objectStore('person').delete(1)

request.onsuccess = function (event) {
    console.log('数据删除成功')
}
// 清空
db.transaction(['person'], 'readwrite').objectStore('person').clear()
遍历
ts
var objectStore = db.transaction('person').objectStore('person')

objectStore.openCursor().onsuccess = function (event) {
    var cursor = event.target.result

    if (cursor) {
        console.log('Id: ' + cursor.key)
        console.log('Name: ' + cursor.value.name)
        console.log('Age: ' + cursor.value.age)
        console.log('Email: ' + cursor.value.email)
        cursor.continue()
    } else {
        console.log('没有更多数据了!')
    }
}
// 直接获取全部数据
db.transaction('person').objectStore('person').getAll()
使用索引
ts
/**
 * 索引的意义在于,可以让你搜索任意字段,也就是说从任意字段拿到数据记录。如果不建立索引,默认只能搜索主键(即从主键取值)。
 */
var transaction = db.transaction(['person'], 'readonly')
var store = transaction.objectStore('person')
var index = store.index('name')
var request = index.get('李四')

request.onsuccess = function (e) {
    var result = e.target.result
    if (result) {
        // ...
    } else {
        // ...
    }
}
检查数据库是否存在
ts
const dbs = await indexedDB.databases()
dbs.some((db) => db.name === dbName)

idb 库

reference

github

  • 安装
shell
pnpm add idb
  • 示例
ts
import { openDB, type DBSchema } from 'idb'

type Schema<T extends DBSchema> = {
    [P in keyof T]: T[P]['indexes'] extends Recordable
        ? {
              indexes: (keyof T[P]['indexes'])[]
          }
        : object
}

interface Config<T extends DBSchema> {
    SCHEMA: Schema<T>
    DB_NAME: string
    DB_VERSION: number
}
// 1. 先定义数据库类型
interface MyDB extends DBSchema {
    todo: {
        key: string
        value: number
    }
    dictionary: {
        value: {
            label: string
            meaning: string
            level: 'cet4' | 'cet6' | 'high_school'
        }
        key: number // 主索引
        indexes: { label: string }
    }
}
// 2. 定义完整信息
const DB_CONFIG: Config<MyDB> = {
    SCHEMA: {
        dictionary: {
            indexes: ['level'],
        },
        todo: {},
    },
    DB_NAME: 'my-db',
    DB_VERSION: 1,
}
export async function demo() {
    const { DB_NAME, DB_VERSION, SCHEMA } = DB_CONFIG
    // 3. 赋值即可
    const db = await openDB<MyDB>(DB_NAME, DB_VERSION, {
        upgrade(db) {
            /*
             * 根据 SCHEMA 创建对应的表
             * indexes 存在的话,就创建对应的索引;
             * value 为对象类型,默认使用 id 作为主键(自增)
             *
             */
            const createdStoreKeys = Object.values(db.objectStoreNames)
            const storeKeys = Object.keys(SCHEMA) as typeof createdStoreKeys
            const unCreate = storeKeys.filter((store) => !createdStoreKeys.includes(store))
            if (unCreate.length) {
                unCreate.forEach((store) => {
                    const indexes = (Reflect.get(SCHEMA, store) as Recordable)['indexes'] as
                        | null
                        | string[]
                    const s = db.createObjectStore(
                        store,
                        indexes
                            ? {
                                  keyPath: 'id',
                                  autoIncrement: true,
                              }
                            : undefined
                    )

                    if (indexes) {
                        // eslint-disable-next-line @typescript-eslint/ban-ts-comment
                        // @ts-expect-error
                        indexes.forEach((index) => s.createIndex(index, index))
                    }
                })
            }
        },
    })

    // value 是对象,那么就可以不传递 key 属性
    db.put('dictionary', {
        label: 'hi',
        meaning: '你好2',
        level: 'cet4',
    })
    // value 是 number,需要传递对应的 key(唯一),相同的时候就是更新
    db.put('todo', 1, 'someKey')
    // 根据索引查询(从 dictionary 中查找索引 level = cet4)
    db.getAllFromIndex('dictionary', 'level', 'cet4')
    // 根据主索引查询(id=1)
    db.get('dictionary', 1)
    // 根据主索引查询(附加 id >= 2 条件)
    db.getAll('dictionary', IDBKeyRange.lowerBound(2))
    // 删除
    db.delete('dictionary', IDBKeyRange.lowerBound(5))
}

Last updated: