# 什么是 indexeddbindexeddb 是浏览器提供的一种本地存储机制,可以存储大量的支持结构化克隆算法 的数据。
# 如何使用 indexeddb# 打开 indexeddb1 2 3 const request = indexedDB.open('todo_database'); // with version const request = indexedDB.open('todo_database', 2);
# 创建 / 删除 indexeddb objectStore1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 // onupgradeneeded 事件会在version变化的时候触发 (创建database也可以理解一种version change, 0 -> 大于0) request.onupgradeneeded = () => { const db = request.result; // 创建objectStore const ob = db.createObjectStore('todo_store'); // 创建索引 ob.createIndex('id', 'id', { unique: true }); objectStore.transaction.oncomplete = () => { // 你可以操作 todo_store objectStore 了 // ... // 删除objectStore db.deleteObjectStore('todo_store'); }; };
// 只有在 onupgradeneeded 中才可以 创建 / 删除 objectStore // 所以,如果你需要创建 / 删除 objectStore,需要监听 onupgradeneeded 事件,同时你要在调用 open 的时候设置一个 version 数值比当前的 version 大。
# 处理 indexeddb1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 request.onsuccess = () => { const db = request.result; // 获取数据库中的数据 const transaction = db.transaction('todo_store'); const store = transaction.objectStore('todo_store'); const request = store.get(1); request.onerror = () => { console.log('error'); }; request.onsuccess = () => { const data = request.result; console.log(data); }; };
indexeddb 处理请求都是通过事件来处理的,所以需要监听事件(onsuccess, onerror)。 db.transaction () 会返回一个事务对象,事务对象可以处理多个 store。同时可以配置事务的 mode(readonly, readwrite)。
# 关闭 indexeddb1 2 // db type is IDBDatabase db.close();
# IDBDatabase 事件close close 事件会在数据库关闭的时候触发 (注意如果是调用的 db.close (), 那么这个事件不会触发)
versionchange versionchange 事件会在数据库的 version 变化的时候触发
# versionchange 有什么妙用吗?背景:对于多个连接到同一个 database 的实例,如果某个触发 onupgradeneeded 事件,那么其他实例需要断开连接,否则该 onupgradeneeded 事件会 blocked。
考虑这样的一个场景:你需要更新 database 的 schema,但是呢,你又有很多个关于该 database 的实例,你需要一个一个的去调用 db.close () 吗? 答案是:不需要,你可以监听 versionchange 事件,当 versionchange 事件触发的时候,你再调用 db.close ()。 1 2 3 4 db.onversionchange = () => { db.close(); // 如果你需要重新连接到database,你可以在未来某个时间节点重新调用open方法进行reconnect }
# IDBCursorIDBCursor 是 indexeddb 提供的一种游标,可以遍历数据库中的数据。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 const request = this.db .transaction('todo_store', 'readonly') .objectStore('todo_store') .openCursor(); const keys: string[] = []; request.onsuccess = e => { const cursor = (e.target as any).result as IDBCursorWithValue; if (!cursor) { // 没有更多数据了 return; } const value = cursor.value // do something with value // ... cursor.continue(); };
# IDBKeyRangeIDBKeyRange 是 indexeddb 提供的一种范围,可以用于查询数据库中的数据。
1 2 3 4 const lower = 'your lower bound'; const upper = 'your upper bound'; const range = IDBKeyRange.bound(lower, upper, false, true); range 包含范围key: `[lower, upper)`
# IDBTransactionIDBTransaction 是 indexeddb 提供的一种事务,所有的读取和写入数据均在事务中完成。
1 2 const transaction = db.transaction('todo_store'); const store = transaction.objectStore('todo_store');
提起这个主要是踩了一个坑 最小 example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <meta name ="viewport" content ="width=device-width, initial-scale=1.0" > <title > IndexedDB Demo</title > </head > <body > <button id ="loadButton" > Load Data</button > <div id ="output" > </div > <script > const request = indexedDB.open ("myDatabase" , 1 ); let objectStore; request.onupgradeneeded = function (event ) { const db = event.target .result ; if (!db.objectStoreNames .contains ("myStore" )) { const store = db.createObjectStore ("myStore" , { keyPath : "id" }); store.transaction .oncomplete = function ( ) { const transaction = db.transaction ("myStore" , "readwrite" ); const objectStore = transaction.objectStore ("myStore" ); objectStore.add ({ id : 1 , name : "Alice" , age : 25 }); objectStore.add ({ id : 2 , name : "Bob" , age : 30 }); }; } }; request.onsuccess = function (event ) { const db = event.target .result ; const transaction = db.transaction ("myStore" , "readwrite" ); objectStore = transaction.objectStore ("myStore" ); console .log ("Database opened successfully" ); const loadButton = document .getElementById ("loadButton" ); loadButton.addEventListener ("click" , function ( ) { const getRequest = objectStore.get (1 ); getRequest.onsuccess = function (event ) { const result = event.target .result ; document .getElementById ("output" ).textContent = `Name: ${result.name} , Age: ${result.age} ` ; }; getRequest.onerror = function (event ) { console .error ("Error retrieving data: " , event.target .error ); }; }); }; request.onerror = function (event ) { console .error ("Database open failed: " , event.target .error ); }; </script > </body > </html >
报错:
1 2 Uncaught TransactionInactiveError: Failed to execute 'get' on 'IDBObjectStore': The transaction has finished. at HTMLButtonElement.<anonymous> (indexedDB-transaction-issue.html:48:48)
原因:
所有请求结束并且控制权返回事件循环之前没有发出其他请求,transaction 会自动提交(finished)。换句话,你可以在上一个请求的成功 / 失败回调中发出请求,但不能在其他异步中处理该事务。对于 finished transaction,任何请求都会报错。 在这个 demo 中,transaction 在 onupgradeneeded 中创建,button click callback 通过闭包获取到了 objectStore。但此时我们的 transaction 已经 finished 了,所以报错 解决方案:在 callback 重新创建一个 transaction