在React应用中使用Dexie.js进行离线数据存储

原文:blog.zhangbing.site/2021/03/16/…css

离线存储应用程序数据已成为现代Web开发中的必要条件。内置的浏览器 localStorage 能够用做简单轻量数据的数据存储,可是在结构化数据或存储大量数据方面却不足。html

最重要的是,咱们只能将字符串数据存储在受XSS攻击的 localStorage 中,而且它没有提供不少查询数据的功能。react

这就是IndexedDB的亮点。使用IndexedDB,咱们能够在浏览器中建立结构化的数据库,将几乎全部内容存储在这些数据库中,并对数据执行各类类型的查询。git

在本文中,咱们将了解IndexedDB的所有含义,以及如何使用Dexie.js(用于IndexedDB的简约包装)处理Web应用程序中的离线数据存储。github

IndexedDB如何工做

IndexedDB是用于浏览器的内置非关系数据库。它使开发人员可以将数据持久存储在浏览器中,即便在脱机时也能够无缝使用Web应用程序。使用IndexedDB时,您会常常看到两个术语:数据库存储和对象存储。让咱们在下面进行探讨。shell

使用IndexedDB建立数据库

IndexedDB数据库对每一个Web应用程序来讲都是惟一的。这意味着一个应用程序只能从与本身运行在同一域或子域的 IndexedDB 数据库中访问数据。数据库是容纳对象存储的地方,而对象存储又包含存储的数据。要使用IndexedDB数据库,咱们须要打开(或链接到)它们:数据库

const initializeDb = indexedDB.open('name_of_database', version)
复制代码

indexedDb.open() 方法中的 name_of_database 参数将用做正在建立的数据库的名称,而 version 参数是一个表明数据库版本的数字。npm

在IndexedDB中,咱们使用对象存储来构建数据库的结构,而且每当要更新数据库结构时,都须要将版本升级到更高的值。这意味着,若是咱们从版本1开始,则下次要更新数据库的结构时,咱们须要将 indexedDb.open() 方法中的版本更改成2或更高版本。编程

使用IndexedDB建立对象存储

对象存储相似于关系数据库(如PostgreSQL)中的表和文档数据库(如MongoDB)中的集合。要在IndexedDB中建立对象存储,咱们须要从以前声明的 initializeDb 变量中调用 onupgradeneeded() 方法:bootstrap

initializeDb.onupgradeneeded = () => {
  const database = initializeDb.result
  database.createObjectStore('name_of_object_store', {autoIncrement: true})
}
复制代码

在上面的代码块中,咱们从 initializeDb.result 属性获取数据库,而后使用其 createObjectStore() 方法建立对象存储。第二个参数 {autoIncrement:true} 告诉IndexedDB自动提供/增长对象存储中项目的ID。

我省略了诸如事务和游标之类的其余术语,由于使用低级IndexedDB API须要进行大量工做。这就是为何咱们须要Dexie.js,它是IndexedDB的简约包装。让咱们看看Dexie如何简化建立数据库,对象存储,存储数据以及从数据库查询数据的整个过程。

使用Dexie离线存储数据

使用Dexie,建立IndexedDB数据库和对象存储很是容易:

const db = new Dexie('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() 方法为数据库建立对象存储。每一个对象存储的值表明了它的结构。例如,当在第一个对象存储中存储数据时,咱们须要提供一个具备属性 nameprice 的对象。++id 选项的做用就像咱们在建立对象存储区时使用的 {autoIncrement:true} 参数同样。

请注意,在咱们的应用程序中使用dexie包以前,咱们须要安装并导入它。当咱们开始构建咱们的演示项目时,咱们将看到如何作到这一点。

咱们将要创建的

对于咱们的演示项目,咱们将使用Dexie.js和React构建一个市场列表应用程序。咱们的用户将可以在市场列表中添加他们打算购买的商品,删除这些商品或将其标记为已购买。

咱们将看到如何使用Dexie useLiveQuery hook来监视IndexedDB数据库中的更改以及在数据库更新时从新呈现React组件。这是咱们的应用程序的外观:

设置咱们的应用

首先,咱们将使用为应用程序的结构和设计建立的GitHub模板。这里有一个模板的连接。点击**Use this template(使用此模板)**按钮,就会用现有的模板为你建立一个新的资源库,而后你就能够克隆和使用这个模板。

或者,在计算机上安装了GitHub CLI的状况下,您能够运行如下命令从市场列表GitHub模板建立名为 market-list-app 的存储库:

gh repo create market-list-app --template ebenezerdon/market-list-template
复制代码

完成此操做后,您能够继续在代码编辑器中克隆并打开您的新应用程序。使用终端在应用程序目录中运行如下命令应安装npm依赖项并启动新应用程序:

npm install && npm start
复制代码

导航到成功消息中的本地URL(一般为http://localhost:3000)时,您应该可以看到新的React应用程序。您的新应用应以下所示:

当您打开 ./src/App.js 文件时,您会注意到咱们的应用程序组件仅包含市场列表应用程序的JSX代码。咱们正在使用Materialize框架中的类进行样式设置,并将其CDN连接包含在 ./public/index.html 文件中。接下来,咱们将看到如何使用Dexie建立和管理数据。

用Dexie建立咱们的数据库

要在咱们的React应用程序中使用Dexie.js进行离线存储,咱们将从在终端中运行如下命令开始,以安装 dexiedexie-react-hooks 软件包:

npm i -s dexie dexie-react-hooks
复制代码

咱们将使用 dexie-react-hooks 包中的 useLiveQuery hook来监视更改,并在对IndexedDB数据库进行更新时从新渲染咱们的React组件。

让咱们将如下导入语句添加到咱们的 ./src/App.js 文件中。这将导入 DexieuseLiveQuery hook:

import Dexie from 'dexie'
import { useLiveQuery } from "dexie-react-hooks";
复制代码

接下来,咱们将建立一个名为 MarketList 的新数据库,而后声明咱们的对象存储 items

const db = new Dexie('MarketList');
db.version(1).stores(
  { items: "++id,name,price,itemHasBeenPurchased" }
)
复制代码

咱们的 items 对象存储将期待一个具备属性namepriceitemHasBeenPurchased 的对象,而 id 将由 Dexie 提供。在将新数据添加到对象存储中时,咱们将为 itemHasBeenPurchased 属性使用默认布尔值 false,而后在咱们从市场清单中购买商品时将其更新为 true

Dexie React hook

让咱们建立一个变量来存储咱们全部的项目。咱们将使用 useLiveQuery 钩子从 items 对象存储中获取数据,并观察其中的变化,这样当 items 对象存储有更新时,咱们的 allItems 变量将被更新,咱们的组件将用新的数据从新渲染。咱们将在 App 组件内部进行:

const App = () => {
  const allItems = useLiveQuery(() => db.items.toArray(), []);
  if (!allItems) return null
  ...
}
复制代码

在上面的代码块中,咱们建立了一个名为 allItems 的变量,并将 useLiveQuery 钩子做为其值。useLiveQuery 钩子的语法相似于React的useEffect钩子,它指望一个函数及其依赖项数组做为参数。咱们的函数参数返回数据库查询。

在这里,咱们以数组格式获取 items 对象存储中的全部数据。在下一行中,咱们使用一个条件来告诉咱们的组件,若是 allItems 变量是undefined,则意味着查询仍在加载中。

将items添加到咱们的数据库

仍在App组件中,让咱们建立一个名为 addItemToDb 的函数,咱们将使用该函数向数据库中添加项目。每当咱们点击“ADD ITEM(添加项目)”按钮时,咱们都会调用此函数。请记住,每次更新数据库时,咱们的组件都会从新渲染。

...
const addItemToDb = async event => {
    event.preventDefault()
    const name = document.querySelector('.item-name').value
    const price = document.querySelector('.item-price').value
    await db.items.add({
      name,
      price: Number(price),
      itemHasBeenPurchased: false
    })
  }
...
复制代码

addItemToDb 函数中,咱们从表单输入字段中获取商品名称和价格值,而后使用 db.[name_of_object_store].add 方法将新商品数据添加到商品对象存储中。咱们还将 itemHasBeenPurchased 属性的默认值设置为 false

从咱们的数据库中删除items

如今咱们有了 addItemToDb 函数,让咱们建立一个名为 removeItemFromDb 的函数以从咱们的商品对象存储中删除数据:

...
const removeItemFromDb = async id => {
  await db.items.delete(id)
}
...
复制代码

更新咱们数据库中的项目

接下来,咱们将建立一个名为 markAsPurchased 的函数,用于将商品标记为已购买。咱们的函数在调用时,会将物品的主键做为第一个参数——在本例中是 id,它将使用这个主键来查询咱们想要标记为购买的物品的数据库。取得商品后,它将其 markAsPurchased 属性更新为 true

...
const markAsPurchased = async (id, event) => {
  if (event.target.checked) {
    await db.items.update(id, {itemHasBeenPurchased: true})
  }
  else {
    await db.items.update(id, {itemHasBeenPurchased: false})
  }
}
...
复制代码

markAsPurchased 函数中,咱们使用 event 参数来获取用户单击的特定输入元素。若是选中其值,咱们将itemHasBeenPurchased 属性更新为 true,不然更新为 falsedb.[name_of_object_store] .update() 方法指望该项目的主键做为其第一个参数,而新对象数据做为其第二个参数。

下面是咱们的 App 组件在这个阶段应该是什么样子。

...
const App = () => {
  const allItems = useLiveQuery(() => db.items.toArray(), []);
  if (!allItems) return null

  const addItemToDb = async event => {
    event.preventDefault()
    const name = document.querySelector('.item-name').value
    const price = document.querySelector('.item-price').value
    await db.items.add({ name, price, itemHasBeenPurchased: false })
  }

  const removeItemFromDb = async id => {
    await db.items.delete(id)
  }

  const markAsPurchased = async (id, event) => {
    if (event.target.checked) {
      await db.items.update(id, {itemHasBeenPurchased: true})
    }
    else {
      await db.items.update(id, {itemHasBeenPurchased: false})
    }
  }
  ...
}
复制代码

如今,咱们建立一个名为 itemData 的变量,以容纳咱们全部商品数据的JSX代码:

...
const itemData = allItems.map(({ id, name, price, itemHasBeenPurchased }) => (
  <div className="row" key={id}> <p className="col s5"> <label> <input type="checkbox" checked={itemHasBeenPurchased} onChange={event => markAsPurchased(id, event)} /> <span className="black-text">{name}</span> </label> </p> <p className="col s5">${price}</p> <i onClick={() => removeItemFromDb(id)} className="col s2 material-icons delete-button"> delete </i> </div>
))
...
复制代码

itemData 变量中,咱们映射了 allItems 数据数组中的全部项目,而后从每一个 item 对象获取属性 idnameprice itemHasBeenPurchased。而后,咱们继续进行操做,并用数据库中的新动态值替换了之前的静态数据。

注意,咱们还使用了 markAsPurchasedremoveItemFromDb 方法做为相应按钮的单击事件侦听器。咱们将在下一个代码块中将 addItemToDb 方法添加到表单的 onSubmit 事件中。

准备好 itemData 后,让咱们将 App 组件的return语句更新为如下JSX代码:

...
return (
  <div className="container"> <h3 className="green-text center-align">Market List App</h3> <form className="add-item-form" onSubmit={event => addItemToDb(event)} > <input type="text" className="item-name" placeholder="Name of item" required/> <input type="number" step=".01" className="item-price" placeholder="Price in USD" required/> <button type="submit" className="waves-effect waves-light btn right">Add item</button> </form> {allItems.length > 0 && <div className="card white darken-1"> <div className="card-content"> <form action="#"> { itemData } </form> </div> </div> } </div>
)
...
复制代码

在return语句中,咱们已将 itemData 变量添加到咱们的项目列表中(items list)。咱们还使用 addItemToDb 方法做为 add-item-formonsubmit 值。

为了测试咱们的应用程序,咱们能够返回到咱们先前打开的React网页。请记住,您的React应用必须正在运行,若是不是,请在终端上运行命令 npm start。您的应用应该可以像下面的演示同样运行:

咱们还可使用条件用Dexie查询咱们的IndexedDB数据库。例如,若是咱们要获取价格高于10美圆的全部商品,则能够执行如下操做:

const items = await db.friends
  .where('price').above(10)
  .toArray();
复制代码

您能够在Dexie文档中查看其余查询方法。

结束语和源码

在本文中,咱们学习了如何使用IndexedDB进行离线存储以及Dexie.js如何简化该过程。咱们还了解了如何使用Dexie useLiveQuery 钩子来监视更改并在每次更新数据库时从新渲染React组件。

因为IndexedDB是浏览器原生的,从数据库中查询和检索数据比每次须要在应用中处理数据时都要发送服务器端API请求要快得多,并且咱们几乎能够在IndexedDB数据库中存储任何东西。

过去使用IndexedDB可能对浏览器的支持是一个大问题,可是如今全部主流浏览器都支持它。在Web应用中使用IndexedDB进行离线存储的诸多优点大于劣势,将Dexie.js与IndexedDB一块儿使用,使得Web开发变得史无前例的有趣。

这是咱们的演示应用程序的GitHub库的连接。

更多文章

相关文章
相关标签/搜索