程序数据已成为现代Web开发中的必需品。内置的浏览器localStorage可以作为简单轻量级数据的数据存储,但对于结构化数据或存储大量数据则显得力不从心。最重要的是,我们只能将字符串数据存储在容易受到XSS攻击的localStorage中,并且它没有提供很多查询数据的功能。这就是IndexedDB的亮点所在。使用IndexedDB,我们可以在浏览器中创建结构化数据库,将几乎所有内容存储在这些数据库中,并对数据执行各种类型的查询。在本文中,我们将了解IndexedDB的全部内容,以及如何使用Dexie.js(IndexedDB的简约包装器)来处理Web应用程序中的离线数据存储。IndexedDB是如何工作的IndexedDB是浏览器的内置非关系数据库。它使开发人员能够将数据持久存储在浏览器中,即使在离线状态下也能无缝地使用Web应用程序。在使用IndexedDB时,您会经常看到两个术语:数据库存储和对象存储。让我们在下面探讨一下。使用IndexedDB创建数据库IndexedDB数据库对于每个Web应用程序都是唯一的。这意味着应用程序只能访问在与其自身相同的域或子域上运行的IndexedDB数据库中的数据。数据库是保存对象存储的地方,对象存储又包含存储的数据。要使用IndexedDB数据库,我们需要打开(或连接到)它们:constantinitializeDb=indexedDB.open("name_of_database",version);indexedDb.open()方法中的name_of_database参数将作为正在创建的数据库的名称,version参数是一个代表数据库版本的数字。在IndexedDB中,我们使用对象存储来构建数据库的结构,每当我们要更新数据库结构时,我们都需要将版本升级到更高的值。这意味着如果我们从版本1开始,下次我们想要更新数据库的结构时,我们需要将indexedDb.open()方法中的版本更改为2或更高版本。使用IndexedDB创建对象存储对象存储类似于关系数据库(如PostgreSQL)中的表和文档数据库(如MongoDB)中的集合。要在IndexedDB中创建对象存储,我们需要从先前声明的initializeDb变量调用onupgradeneeded()方法:true,});};在上面的代码块中,我们从initializeDb.result属性获取数据库,然后使用其createObjectStore()方法创建对象存储。第二个参数{autoIncrement:true}告诉IndexedDB自动提供/增加对象存储中项目的ID。我省略了事务和游标等其他术语,因为使用低级IndexedDBAPI需要大量工作。这就是我们需要Dexie.js的原因,它是IndexedDB的简约包装器。让我们看看Dexie如何简化创建数据库、对象存储、存储数据和从数据库查询数据的整个过程。使用Dexie离线存储数据使用Dexie,创建IndexedDB数据库和对象存储非常容易:constdb=newDexie("exampleDatabase");db.version(1).stores({name_of_object_store:"++id,name,price",name_of_another_object_store:"++id,title",});在上面的代码块中,我们创建了一个名为exampleDatabase的新数据库,并将其作为值分配给db变量。我们使用db.version(version_number).stores()方法为数据库创建对象存储。每个对象存储代表其结构的值。例如,在第一个对象存储中存储数据时,我们需要提供一个具有属性名称和价格的对象。++id选项的作用类似于我们在创建对象存储时使用的{autoIncrement:true}参数。请注意,我们需要先安装并导入dexie包,然后才能在我们的应用程序中使用它。当我们开始构建我们的演示项目时,我们将看到如何做到这一点。我们将构建什么对于我们的演示项目,我们将使用Dexie.js和React构建一个市场列表应用程序。我们的用户将能够在市场列表中添加他们打算购买的商品、删除这些商品或将它们标记为已购买。我们将看到如何使用DexieuseLiveQuery挂钩来监视IndexedDB数据库中的更改,并在数据库更新时重新渲染React组件。我们的应用程序如下所示:设置我们的应用程序首先,我们将使用为应用程序的结构和设计创建的GitHub模板。这是模板的链接。单击**使用此模板**按钮将使用现有模板为您创建一个新的资源库,然后您可以克隆并使用此模板。或者,在计算机上安装GitHubCLI后,您可以运行以下命令从市场列表GitHub模板创建名为market-list-app的存储库:ghrepocreatemarket-list-app--templateebenezerdon/market-list-template完成后这样做,您可以继续克隆并在代码编辑器中打开您的新应用程序。使用终端在应用程序目录中运行以下命令应该安装npm依赖项并启动新应用程序:npminstall&&npmstart当导航到成功消息中的本地URL(通常是http://localhost:3000)时,您应该能够查看新的React应用程序。您的新应用应如下所示:当您打开./src/App.js文件时,您会注意到我们的应用组件仅包含市场列表应用的JSX代码。我们正在使用Materialize框架中的一个类来进行样式设置,并将其CDN链接包含在./public/index.html文件中。接下来,我们将了解如何使用Dexie创建和管理数据。使用Dexie创建我们的数据库要在我们的React应用程序中使用Dexie.js进行离线存储,我们首先在终端中运行以下命令来安装dexie和dexie-react-hooks包:npmi-sdexiedexie-react-hooks我们将使用dexie-react-hooks包中的useLiveQuery挂钩来观察变化,并在对IndexedDB数据库进行更新时重新呈现我们的React组件。让我们将以下导入语句添加到我们的./src/App.js文件中。这将导入Dexie和useLiveQuery钩子:importDexiefrom"dexie";import{useLiveQuery}from"dexie-react-hooks";接下来,我们将创建一个名为MarketList的新数据库,然后声明我们的对象存储项目:constdb=newDexie("MarketList");db.version(1).stores({items:"++id,name,price,itemHasBeenPurchased",});我们的项目对象存储需要一个具有属性名称、价格和itemHasBeenPurchased对象的项目,并且ID将由Dexie提供。当新数据添加到对象存储时,我们将为itemHasBeenPurchased属性使用默认布尔值false,然后当我们从市场列表中购买商品时将其更新为true。DexieReacthook让我们创建一个变量来存储我们所有的项目。我们将使用useLiveQuery钩子从items对象存储中获取数据并观察其中的变化,这样当items对象存储更新时,我们的allItems变量将被更新,我们的组件将使用新数据重新呈现。我们将在App组件内部进行:上面代码中的constApp=()=>{constallItems=useLiveQuery(()=>db.items.toArray(),[]);if(!allItems)returnull...}块,我们创建一个名为allItems的变量,并将useLiveQuery挂钩作为其值。useLiveQuery钩子的语法类似于React的useEffect钩子,它需要一个函数及其依赖项数组作为参数。我们的函数参数返回数据库查询。在这里,我们以数组格式获取项目对象存储中的所有数据。在下一行中,我们使用一个条件来告诉我们的组件,如果allItems变量未定义,则意味着查询仍在加载。将项目添加到我们的数据库仍然在App组件中,让我们创建一个名为addItemToDb的函数,我们将使用它向我们的数据库添加项目。每次单击“添加项目”按钮时,我们都会调用此函数。请记住,每次更新数据库时,我们的组件都会重新呈现。...constaddItemToDb=asyncevent=>{event.preventDefault()constname=document.querySelector('.item-name').valueconstprice=document.querySelector('.item-price').valueawaitdb.items.add({name,price:Number(price),itemHasBeenPurchased:false})}...在addItemToDb函数中,我们从表单输入字段中获取商品名称和价格值,然后使用db.[name_of_object_store].add方法添加将新产品数据放入项目对象存储中。我们还将itemHasBeenPurchased属性的默认值设置为false。从我们的数据库中删除项目现在我们有了addItemToDb函数,让我们创建一个名为removeItemFromDb的函数来从我们的项目对象存储中删除数据:...constremoveItemFromDb=asyncid=>{awaitdb.items.delete(id)}...update数据库中的项目接下来,我们将创建一个名为markAsPurchased的函数,用于将项目标记为已购买。我们的函数将以项目的主键作为其第一个参数调用-在本例中为id,它将使用此键在数据库中查询我们要标记为已购买的项目。获取项目后,它将其markAsPurchased属性更新为true:...constmarkAsPurchased=async(id,event)=>{if(event.target.checked){awaitdb.items.update(id,{itemHasBeenPurchased:true})}else{awaitdb.items.update(id,{itemHasBeenPurchased:false})}}...在markAsPurchased函数中,我们使用event参数获取用户点击的具体输入元素。如果选中了itemHasBeenPurchased属性,我们将其更新为true,否则为false。db.[name_of_object_store].update()方法需要项目的主键作为它的第一个参数,新的对象数据作为它的第二个参数。这是我们的App组件在这个阶段应该看起来的样子。...constApp=()=>{constallItems=useLiveQuery(()=>db.items.toArray(),[]);if(!allItems)returnnullconstaddItemToDb=asyncevent=>{event.preventDefault()constname=document。querySelector('.item-name').valueconstprice=document.querySelector('.item-price').valueawaitdb.items.add({name,price,itemHasBeenPurchased:false})}constremoveItemFromDb=asyncid=>{awaitdb.items.delete(id)}constmarkAsPurchased=async(id,event)=>{if(event.target.checked){awaitdb.items.update(id,{itemHasBeenPurchased:true})}else{awaitdb.items.update(id,{itemHasBeenPurchased:false})}}...}现在我们创建一个名为itemData的变量来保存我们所有项目数据的JSX代码:...constitemData=allItems.map(({id,name,price,itemHasBeenPurchased})=>(
