数据存储是大多数 Web
应用程序的重要组成部分,从跟踪用户数据到应用程序数据。随着更快、更强大的 Web
应用程序的快速开发,需要高效的客户端存储来帮助开发。
多年来,Web
上的客户端存储已经发生了很大的变化,从用于存储用户数据的 cookie
到 WebSQL
(目前已弃用)的出现,它允许开发人员将数据存储在浏览器中的 SQL
数据库中,进而允许熟悉 SQL
的开发轻松构建健壮的应用程序。
IndexedDB
是WebSQL
的替代品,提供比以前更多的存储容量。在本文中,我们将探讨如何使用和设置 IndexedDB
进行 Web
端的数据存储,以及如何使用 API
操作其数据。
IndexedDB
是用于客户端存储的一种成熟的、持久的 NoSQL
存储系统,可在浏览器中使用,允许存储不同类型的数据,例如:
Blob
IndexedDB
可用于各种场景,例如缓存、PWA
、游戏等,并且还支持事务。它的开发是为了有效地满足 Web
应用程序的多种需求。
为了更好的展示IndexDB
的使用,我们将创建一个基本的 TODO
网页 。
开发一个添加功能以学习如何将数据保存到数据库,另一个功能用于查看所有代办数据,以及一个删除功能。也就是增删改查。
首先我们先创建一个简单的html
基础结构。
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>TODO APPtitle>
<script src="index.js" defer>script>
<style>
.add, .view {
padding: 30px;
width: 40%;
}
.add {
background: #ebe6e6;
}
section {
padding: 10px;
background: #3182d4;
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: center;
}
h1 {
margin: 0;
}
ol {
list-style-type: none;
}
div {
margin-bottom: 10px;
}
style>
head>
<body>
<section>
<aside class="view">
<h2>TODOsh2>
<div class="todos">
<ol>ol>
div>
aside>
<aside class="add">
<h2>添加 Todoh2>
<form>
<div>
<label for="title">标题:label>
<input id="title" type="text" required />
div>
<div>
<label for="desc">描述label>
<input id="desc" type="text" required />
div>
<div>
<button>保存button>
div>
form>
aside>
section>
body>
html>
通过script
标签引入核心功能。
首先,我们需要创建数据库;然后,我们可以通过创建一个对象存储(类似于 SQL
中的表)来初始化它,我们将使用它来存储每个项目的详细信息。
let db;
const openOrCreateDB = window.indexedDB.open('todo_db', 1);
openOrCreateDB.addEventListener('error', () =>
console.error('打开 IndexDB 失败')
);
openOrCreateDB.addEventListener('success', () => {
console.log('打开 IndexDB 成功');
db = openOrCreateDB.result;
});
openOrCreateDB.addEventListener('upgradeneeded', (init) => {
db = init.target.result;
db.onerror = () => {
console.error('数据库加载失败');
};
const table = db.createObjectStore('todo_tb', {
keyPath: 'id',
autoIncrement: true,
});
table.createIndex('title', 'title', { unique: false });
table.createIndex('desc', 'desc', { unique: false });
});
如上所示,创建了一个名为todo_db
的数据库,然后创建了一个名为todo_tb
的对象存储,其中包含两个索引title
以及desc
。这些索引允许在存储中复制其值,这类似于在 SQL
中创建表,然后创建两列。
接下来,要添加保存功能(也就插入),我们继续检索输入到表单中的值,然后将它们保存到数据库中:
const todos = document.querySelector('ol');
const form = document.querySelector('form');
const todoTitle = document.querySelector('#title');
const todoDesc = document.querySelector('#desc');
const submit = document.querySelector('button');
form.addEventListener('submit', addTodo);
function addTodo(e) {
e.preventDefault();
const newTodo = { title: todoTitle.value, body: todoDesc.value };
const transaction = db.transaction(['todo_tb'], 'readwrite');
const objectStore = transaction.objectStore('todo_tb');
const query = objectStore.add(newTodo);
query.addEventListener('success', () => {
todoTitle.value = '';
todoDesc.value = '';
});
transaction.addEventListener('complete', () => {
showTodos();
});
transaction.addEventListener('error', () => console.log('Transaction error'));
}
要确认保存功能是否有效,可以打开浏览器的检查功能。在 Chrome
浏览器中,可以在“application
(应用程序)”选项卡下的“Storage
(存储)”中看到 IndexedDB
。
如下图所示,我们创建了数据库并将第一个待办事项保存到对象存储中:todo_tb
。
为了在用户加载页面时显示可用的待办事项,并提供以前添加和删除的待办事项的视图,我们将创建一个名为 showTodos
的方法。
function showTodos() {
while (todos.firstChild) {
todos.removeChild(todos.firstChild);
}
const objectStore = db.transaction('todo_tb').objectStore('todo_tb');
objectStore.openCursor().addEventListener('success', (e) => {
const pointer = e.target.result;
if (pointer) {
const listItem = document.createElement('li');
const h3 = document.createElement('h3');
const pg = document.createElement('p');
listItem.appendChild(h3);
listItem.appendChild(pg);
todos.appendChild(listItem);
h3.textContent = pointer.value.title;
pg.textContent = pointer.value.body;
listItem.setAttribute('data-id', pointer.value.id);
const deleteBtn = document.createElement('button');
listItem.appendChild(deleteBtn);
deleteBtn.textContent = 'Remove';
deleteBtn.addEventListener('click', deleteItem);
pointer.continue();
} else {
if (!todos.firstChild) {
const listItem = document.createElement('li');
listItem.textContent = 'No Todo.';
todos.appendChild(listItem);
}
}
});
}
这个方法从存储中获取待办事项,循环遍历为每个事项创建一个li
元素,并将元素添加到网页上的列表元素中,并将每个待办事项元素中添加一个data-id
(数据库中的唯一ID
)属性,用于后续的删除。
把这个方法添加到IndexDB
打开成功的监听中:
openOrCreateDB.addEventListener('success', () => {
console.log('打开 IndexDB 成功');
db = openOrCreateDB.result;
showTodos();
});
最后添加一个删除功能。
function deleteItem(e) {
const todoId = Number(e.target.parentNode.getAttribute('data-id'));
const transaction = db.transaction(['todo_tb'], 'readwrite');
const objectStore = transaction.objectStore('todo_tb');
objectStore.delete(todoId);
transaction.addEventListener('complete', () => {
e.target.parentNode.parentNode.removeChild(e.target.parentNode);
alert(`Todo with id of ${todoId} deleted`);
console.log(`Todo:${todoId} deleted.`);
if (!todos.firstChild) {
const listItem = document.createElement('li');
listItem.textContent = 'No Todo.';
todos.appendChild(listItem);
}
});
transaction.addEventListener('error', () => console.log('Transaction error'));
}
这会使用传递给方法的唯一 ID
删除特定的待办事项,并从网页中删除该元素。删除存储中的最后一个待办事项后,它会在待办事项列表的位置显示“空列表”提示。
要确认待办事项已从数据库中删除,请继续检查网页并单击应用程序选项卡。可以看出,现在不包含任何项目
IndexedDB
还允许开发人员递增数据库版本。打开数据库时,需要指定所需的版本号。
window.indexedDB.open('todo_db', 1);
如果数据库不存在,则将使用指定的版本创建该数据库。如果数据库已存在,则检查版本号。
如果在 open
方法调用期间指定的版本号高于现有版本,则会通过该事件触发onUpgradeNeeded
版本更改事件。此事件允许我们执行数据库架构更改或数据迁移。
这里需要注意的一点是,删除以前的对象存储以添加新选项,创建新存储时也会删除旧存储中的所有其他数据。在升级数据库之前,请注意读出旧内容并将其保存在其他位置。
不同的 Web
浏览器对 IndexedDB
中可以存储的最大数据量施加了限制。这些限制因浏览器而异,范围从几m到几百m不等。