被前端存储坑到崩溃?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 函数用于渲染待办事项列表。
    • 表单提交事件监听器用于添加新的待办事项。
相关推荐
全栈师5 小时前
LigerUI下frm与grid的交互
java·前端·数据库
无尽夏_5 小时前
CSS3(前端基础)
前端·css·1024程序员节
温宇飞5 小时前
Next.js 简述 - React 全栈框架
前端
百花~5 小时前
前端三剑客之一 CSS~
前端·css
青天诀5 小时前
React 中 setTimeout 获取不到最新 State 的原因及解决方案
前端·react.js
拉不动的猪5 小时前
闭包实际项目中应用场景有哪些举例
前端·javascript·面试
专注前端30年5 小时前
【Vue2】基础知识汇总与实战指南
开发语言·前端·vue
懵圈5 小时前
第02章:使用Vite初始化项目
前端
CodeCraft Studio5 小时前
前端表格工具AG Grid 34.3 发布:重磅引入AI工具包,全面支持 React 19.2!
前端·人工智能·react.js·angular·ag grid·前端表格工具·透视分析