被前端存储坑到崩溃?IndexedDB 高效用法帮你少走 90% 弯路

IndexedDB 是一种在浏览器中提供事务性的键值对存储的低级 API。它允许你在用户的浏览器中存储大量结构化数据,并且可以对其进行高效的搜索、更新和删除操作。IndexedDB 适用于需要离线存储和快速访问大量数据的应用程序,如 Progressive Web Apps (PWAs) 和单页应用程序 (SPAs)。本文将详细介绍如何在前端项目中高效使用 IndexedDB。

1. IndexedDB 基本概念

1.1 数据库

数据库是存储对象集合的地方。每个数据库都有一个名称和版本号。

1.2 对象存储空间(Object Store)

对象存储空间类似于关系型数据库中的表。每个对象存储空间可以包含一组对象,并且每个对象都有一个唯一的键。

1.3 索引(Index)

索引用于快速查找对象存储空间中的对象。索引可以基于对象的属性创建,从而加速查询操作。

1.4 事务(Transaction)

事务是一组操作的集合,这些操作要么全部成功,要么全部失败。事务可以保证数据的一致性和完整性。

1.5 请求(Request)

请求用于执行数据库操作,如添加、删除或获取数据。请求是异步的,可以通过事件监听器处理结果。

2. IndexedDB 基本操作

2.1 打开数据库

javascript 复制代码
const request = indexedDB.open('myDatabase', 1);

request.onupgradeneeded = function(event) {
    const db = event.target.result;
    // 创建对象存储空间
    if (!db.objectStoreNames.contains('myStore')) {
        const objectStore = db.createObjectStore('myStore', { keyPath: 'id', autoIncrement: true });
        // 创建索引
        objectStore.createIndex('nameIndex', 'name', { unique: false });
    }
};

request.onsuccess = function(event) {
    const db = event.target.result;
    console.log('Database opened successfully');
};

request.onerror = function(event) {
    console.error('Database error:', event.target.errorCode);
};

2.2 添加数据

javascript 复制代码
function addData(db, data) {
    const transaction = db.transaction(['myStore'], 'readwrite');
    const objectStore = transaction.objectStore('myStore');

    const request = objectStore.add(data);

    request.onsuccess = function(event) {
        console.log('Data added successfully');
    };

    request.onerror = function(event) {
        console.error('Error adding data:', event.target.errorCode);
    };
}

// 使用示例
const db = request.result;
addData(db, { name: 'John Doe', age: 30 });

2.3 获取数据

javascript 复制代码
function getData(db, id) {
    const transaction = db.transaction(['myStore'], 'readonly');
    const objectStore = transaction.objectStore('myStore');

    const request = objectStore.get(id);

    request.onsuccess = function(event) {
        const data = event.target.result;
        console.log('Data retrieved:', data);
    };

    request.onerror = function(event) {
        console.error('Error retrieving data:', event.target.errorCode);
    };
}

// 使用示例
getData(db, 1);

2.4 更新数据

javascript 复制代码
function updateData(db, id, newData) {
    const transaction = db.transaction(['myStore'], 'readwrite');
    const objectStore = transaction.objectStore('myStore');

    const request = objectStore.get(id);

    request.onsuccess = function(event) {
        const data = event.target.result;
        if (data) {
            Object.assign(data, newData);
            const updateRequest = objectStore.put(data);

            updateRequest.onsuccess = function() {
                console.log('Data updated successfully');
            };

            updateRequest.onerror = function(event) {
                console.error('Error updating data:', event.target.errorCode);
            };
        } else {
            console.error('Data not found');
        }
    };

    request.onerror = function(event) {
        console.error('Error retrieving data:', event.target.errorCode);
    };
}

// 使用示例
updateData(db, 1, { age: 31 });

2.5 删除数据

javascript 复制代码
function deleteData(db, id) {
    const transaction = db.transaction(['myStore'], 'readwrite');
    const objectStore = transaction.objectStore('myStore');

    const request = objectStore.delete(id);

    request.onsuccess = function(event) {
        console.log('Data deleted successfully');
    };

    request.onerror = function(event) {
        console.error('Error deleting data:', event.target.errorCode);
    };
}

// 使用示例
deleteData(db, 1);

3. 使用 Promise 封装 IndexedDB 操作

为了简化异步操作,可以使用 Promise 封装 IndexedDB 的基本操作。

javascript 复制代码
function openDatabase(name, version) {
    return new Promise((resolve, reject) => {
        const request = indexedDB.open(name, version);

        request.onupgradeneeded = function(event) {
            const db = event.target.result;
            if (!db.objectStoreNames.contains('myStore')) {
                const objectStore = db.createObjectStore('myStore', { keyPath: 'id', autoIncrement: true });
                objectStore.createIndex('nameIndex', 'name', { unique: false });
            }
        };

        request.onsuccess = function(event) {
            resolve(event.target.result);
        };

        request.onerror = function(event) {
            reject(event.target.error);
        };
    });
}

function addData(db, data) {
    return new Promise((resolve, reject) => {
        const transaction = db.transaction(['myStore'], 'readwrite');
        const objectStore = transaction.objectStore('myStore');

        const request = objectStore.add(data);

        request.onsuccess = function(event) {
            resolve(event.target.result);
        };

        request.onerror = function(event) {
            reject(event.target.error);
        };
    });
}

function getData(db, id) {
    return new Promise((resolve, reject) => {
        const transaction = db.transaction(['myStore'], 'readonly');
        const objectStore = transaction.objectStore('myStore');

        const request = objectStore.get(id);

        request.onsuccess = function(event) {
            resolve(event.target.result);
        };

        request.onerror = function(event) {
            reject(event.target.error);
        };
    });
}

function updateData(db, id, newData) {
    return new Promise((resolve, reject) => {
        const transaction = db.transaction(['myStore'], 'readwrite');
        const objectStore = transaction.objectStore('myStore');

        const request = objectStore.get(id);

        request.onsuccess = function(event) {
            const data = event.target.result;
            if (data) {
                Object.assign(data, newData);
                const updateRequest = objectStore.put(data);

                updateRequest.onsuccess = function() {
                    resolve();
                };

                updateRequest.onerror = function(event) {
                    reject(event.target.error);
                };
            } else {
                reject(new Error('Data not found'));
            }
        };

        request.onerror = function(event) {
            reject(event.target.error);
        };
    });
}

function deleteData(db, id) {
    return new Promise((resolve, reject) => {
        const transaction = db.transaction(['myStore'], 'readwrite');
        const objectStore = transaction.objectStore('myStore');

        const request = objectStore.delete(id);

        request.onsuccess = function(event) {
            resolve();
        };

        request.onerror = function(event) {
            reject(event.target.error);
        };
    });
}

3.1 使用示例

javascript 复制代码
openDatabase('myDatabase', 1)
    .then(db => {
        return addData(db, { name: 'John Doe', age: 30 });
    })
    .then(id => {
        console.log('Data added with ID:', id);
        return getData(db, id);
    })
    .then(data => {
        console.log('Data retrieved:', data);
        return updateData(db, data.id, { age: 31 });
    })
    .then(() => {
        console.log('Data updated successfully');
        return deleteData(db, data.id);
    })
    .then(() => {
        console.log('Data deleted successfully');
    })
    .catch(error => {
        console.error('Error:', error);
    });

4. 使用 idb 库简化操作

idb 是一个用于简化 IndexedDB 操作的库,提供了更简洁的 API。

4.1 安装 idb

bash 复制代码
npm install idb

4.2 使用 idb 进行操作

javascript 复制代码
import { openDB } from 'idb';

async function openDatabase() {
    const db = await openDB('myDatabase', 1, {
        upgrade(db) {
            if (!db.objectStoreNames.contains('myStore')) {
                const objectStore = db.createObjectStore('myStore', { keyPath: 'id', autoIncrement: true });
                objectStore.createIndex('nameIndex', 'name', { unique: false });
            }
        },
    });
    return db;
}

async function addData(db, data) {
    const tx = db.transaction('myStore', 'readwrite');
    const store = tx.objectStore('myStore');
    await store.add(data);
    await tx.done;
}

async function getData(db, id) {
    const tx = db.transaction('myStore', 'readonly');
    const store = tx.objectStore('myStore');
    return store.get(id);
}

async function updateData(db, id, newData) {
    const tx = db.transaction('myStore', 'readwrite');
    const store = tx.objectStore('myStore');
    const data = await store.get(id);
    if (data) {
        Object.assign(data, newData);
        await store.put(data);
    } else {
        throw new Error('Data not found');
    }
    await tx.done;
}

async function deleteData(db, id) {
    const tx = db.transaction('myStore', 'readwrite');
    const store = tx.objectStore('myStore');
    await store.delete(id);
    await tx.done;
}

// 使用示例
(async () => {
    try {
        const db = await openDatabase();
        const id = await addData(db, { name: 'John Doe', age: 30 });
        console.log('Data added with ID:', id);
        const data = await getData(db, id);
        console.log('Data retrieved:', data);
        await updateData(db, data.id, { age: 31 });
        console.log('Data updated successfully');
        await deleteData(db, data.id);
        console.log('Data deleted successfully');
    } catch (error) {
        console.error('Error:', error);
    }
})();

5. 高效使用 IndexedDB 的最佳实践

5.1 使用事务

尽量将多个操作放在同一个事务中,以减少事务的开销并提高性能。

5.2 创建索引

为经常查询的字段创建索引,可以显著提高查询速度。

5.3 数据分片

如果需要存储大量数据,可以考虑将数据分片存储在不同的对象存储空间中。

5.4 异步操作

IndexedDB 是异步的,充分利用异步操作可以避免阻塞主线程,提高应用的响应速度。

5.5 错误处理

始终为每个请求添加错误处理逻辑,以便在出现问题时能够及时捕获并处理。

5.6 数据版本管理

使用版本号来管理数据库的升级和降级,确保数据的一致性和完整性。

6. 示例:构建一个简单的待办事项应用

下面是一个使用 IndexedDB 构建的简单待办事项应用的示例。

6.1 HTML 结构

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Todo App</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            margin: 20px;
        }
        #todo-form {
            margin-bottom: 20px;
        }
        #todo-list {
            list-style-type: none;
            padding: 0;
        }
        .todo-item {
            display: flex;
            align-items: center;
            justify-content: space-between;
            margin-bottom: 10px;
        }
        .todo-item button {
            margin-left: 10px;
        }
    </style>
</head>
<body>
    <h1>Todo List</h1>
    <form id="todo-form">
        <input type="text" id="todo-input" placeholder="Add a new todo" required>
        <button type="submit">Add</button>
    </form>
    <ul id="todo-list"></ul>

    <script src="app.js"></script>
</body>
</html>

6.2 JavaScript 代码 (app.js)

javascript 复制代码
import { openDB } from 'idb';

let db;

async function openDatabase() {
    db = await openDB('todoDatabase', 1, {
        upgrade(db) {
            if (!db.objectStoreNames.contains('todos')) {
                db.createObjectStore('todos', { keyPath: 'id', autoIncrement: true });
            }
        },
    });
}

async function addTodo(todo) {
    const tx = db.transaction('todos', 'readwrite');
    const store = tx.objectStore('todos');
    await store.add(todo);
    await tx.done;
    renderTodos();
}

async function getTodos() {
    const tx = db.transaction('todos', 'readonly');
    const store = tx.objectStore('todos');
    return store.getAll();
}

async function deleteTodo(id) {
    const tx = db.transaction('todos', 'readwrite');
    const store = tx.objectStore('todos');
    await store.delete(id);
    await tx.done;
    renderTodos();
}

async function renderTodos() {
    const todos = await getTodos();
    const todoList = document.getElementById('todo-list');
    todoList.innerHTML = '';
    todos.forEach(todo => {
        const li = document.createElement('li');
        li.className = 'todo-item';
        li.textContent = todo.text;
        const deleteButton = document.createElement('button');
        deleteButton.textContent = 'Delete';
        deleteButton.addEventListener('click', () => deleteTodo(todo.id));
        li.appendChild(deleteButton);
        todoList.appendChild(li);
    });
}

document.getElementById('todo-form').addEventListener('submit', async (event) => {
    event.preventDefault();
    const input = document.getElementById('todo-input');
    const text = input.value.trim();
    if (text) {
        await addTodo({ text });
        input.value = '';
    }
});

(async () => {
    await openDatabase();
    renderTodos();
})();

6.3 解释

  1. HTML 结构

    • 包含一个表单用于添加新的待办事项。
    • 包含一个无序列表用于显示所有待办事项。
  2. JavaScript 代码

    • 使用 idb 库简化 IndexedDB 的操作。
    • 打开数据库并创建 todos 对象存储空间。
    • 提供 addTodogetTodosdeleteTodo 函数来操作数据。
    • renderTodos 函数用于渲染待办事项列表。
    • 表单提交事件监听器用于添加新的待办事项。
相关推荐
leafyyuki几秒前
两行 CSS 搞定筛选条行尾对齐,Element Plus 表单布局终极方案
前端
着迷不白1 分钟前
六、Bash Shell 与进程管理
前端·chrome
A不落雨滴AI1 分钟前
DKERP 客户端重构:30天从零到一的架构演进之路
前端
Xp021911036 分钟前
知网研学、万方、WPS、大以论文四大排版工具横评,新用户免费排版等你领!
前端·css·html·生活·wps·论文排版
全栈技术负责人7 分钟前
老项目新需求AI前端开发指南
前端·ai编程
周凡12316 分钟前
AI 时代的 Web JavaScript 逆向分析实践与思考
前端·javascript·人工智能
jerryinwuhan22 分钟前
marker BiBERTo解释
java·前端·人工智能
zhoumeina9930 分钟前
分段创建产品,tab 页切换又要保留缓存
前端·javascript
SilentSamsara31 分钟前
命令行工具开发:Click/Typer + 打包为独立二进制
linux·服务器·开发语言·前端·python·青少年编程·fastapi
恋猫de小郭34 分钟前
能在手机本地跑的图像生成模型 Bonsai Image ,效果还不错
前端·aigc·ai编程