事实上,很多年前,在我第一次在Adobe任职期间,我就对网络的客户端存储机制有了相当深入的了解。当时,"HTML5 "是一个热门词汇,很多人都在谈论网络的改进功能,但在我看来,很多讨论都集中在Canvas等更多的视觉组件上。对我来说,我对新的表单字段和存储等事情更感兴趣。我花了很多时间研究各种存储数据的方法,我甚至写了一本关于这个主题的书。但在花了很多时间研究之后,我又转向了其他话题。
现在--许多年后--它又出现在我的脑海中,特别是IndexedDB。当我第一次深入研究这个话题时,我专注于按原样使用它,只是原始 API,而没有深入研究辅助库。我想现在是一个很好的时机来研究一下外面的一些选择,看看哪一个最适合我的开发。IndexedDB不一定很难使用,但它有点复杂,需要一点规划。与LocalStage相比,它要困难得多,但经过一些练习,还是绝对可用的。然而,如果有一个好的工具库,它当然可以更简单。
由于我在过去已经介绍过如何使用 IndexedDB,所以我不会再讲了,但我要利用这个机会链接到学习任何网络相关知识的最佳资源,即 mdn 网络文档。他们的IndexedDB文档非常好,而且深入到了概念和 API 中。
在这第一篇博文中,我将演示一个使用 IndexedDB 的简单应用,完全没有任何辅助库。这是一个简单的 "Contacts "应用程序(见我之前的文章,内容类似),它存储了一个人的列表,包括名字、姓氏和电子邮件属性。界面列出了联系人,并在右边提供了一个表单。右边的表格可用于创建新的联系人或编辑现有的联系人。
我将在最后分享该应用程序的完整代码,我将跳过谈论DOM方法之类的问题。相反,让我们把重点放在 IndexedDB 的部分。
初始化数据库
任何对 IndexedDB 的使用都需要打开一个与数据库的连接,并处理初始对象存储的创建。使事情更加复杂的是,IndexedDB 支持版本控制,你只允许在版本改变时对数据库进行修改。我在一个函数中处理所有这些:
async function initDb() {
return new Promise((resolve, reject) => {
let request = indexedDB.open('contacts', 1);
request.onerror = event => {
alert('Error Event, check console');
console.error(event);
}
request.onupgradeneeded = event => {
console.log('idb onupgradeneeded firing');
let db = event.target.result;
let objectStore = db.createObjectStore('contacts', { keyPath: 'id', autoIncrement:true });
objectStore.createIndex('lastname', 'lastname', { unique: false });
};
request.onsuccess = event => {
resolve(event.target.result);
};
});
}
你会注意到我正在返回一个承诺,这样我就可以更容易地使用它。我打开数据库,监听一个onupgradeneeded 事件,这将在用户第一次点击页面时触发,并设置对象存储。对于我的联系人,我想要一个自动递增的键,名为id 。不是太糟糕,但在这个函数之外,我可以随便做:
db = await initDb();
我喜欢async/await,就像我喜欢一块好的饼干。
现在让我们来看看各种CRUD(创建/读取/更新/删除)函数。
获取所有联系人
获取我所有的联系人,以便我可以将他们呈现在一个表中,这并不难,因为IndexedDB有个getAll() API:
async function getContacts() {
return new Promise((resolve, reject) => {
let transaction = db.transaction(['contacts'], 'readonly');
transaction.onerror = event => {
reject(event);
};
let store = transaction.objectStore('contacts');
store.getAll().onsuccess = event => {
resolve(event.target.result);
};
});
}
回到调用代码中,我可以直接做。
let contacts = await getContacts();
结果是在一个数组中的简单JavaScript对象,所以使用起来并不困难。
获取一个联系人
获取一个联系人需要一个主键。在我的例子中,我使用了id 属性,所以一旦我知道了这个,我就可以使用get(key) 来获取记录。这里是那个函数:
async function getContact(key) {
return new Promise((resolve, reject) => {
let transaction = db.transaction(['contacts'], 'readonly');
transaction.onerror = event => {
reject(event);
};
let store = transaction.objectStore('contacts');
store.get(key).onsuccess = event => {
resolve(event.target.result);
};
});
}
顺便说一下,你会注意到,有时我等待事务发出成功信息,有时我等待某个特定请求发出成功信息。这有点不一致,但我也认为这也是可以的。不过这也说明了使用IndexedDB的复杂性。下面是一个被调用的例子。
let contact = await getContact(key);
保存联系人
然而,IndexedDB 有点简单的一种方式是存储记录。虽然有一个 API 来添加记录,但也有一个记录的更新,在需要的时候可以很好地处理创建新记录。这是通过put ,我所需要做的就是传递一个带有id 值的对象,或者不传递--它只是做它需要做的事情:
async function persistContact(contact) {
return new Promise((resolve, reject) => {
let transaction = db.transaction(['contacts'], 'readwrite');
transaction.oncomplete = event => {
resolve();
};
transaction.onerror = event => {
reject(event);
};
let store = transaction.objectStore('contacts');
store.put(contact);
});
}
再一次,使用这个方法很简单。
await persistContact(contact);
删除联系人
对于我们需要的最后一个CRUD方法,我设置了一个删除方法。这需要一个主键:
async function removeContact(key) {
return new Promise((resolve, reject) => {
let transaction = db.transaction(['contacts'], 'readwrite');
transaction.oncomplete = event => {
resolve();
};
transaction.onerror = event => {
reject(event);
};
let store = transaction.objectStore('contacts');
store.delete(key);
});
}
这里是使用它的代码:
await removeContact(key);
整个过程
总而言之,一旦你将复杂性封装到函数中,使用 IndexedDB 并不困难,而且一旦你掌握了它,async/await 会使生活变得更加简单。但我真的很好奇,在我开始使用一两个辅助库之后会发生什么。对于完整的演示,你可以使用下面的CodePen演示。请原谅我的格式有点不妥--它在CodePen网站上看起来更好。下一部分见!