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

更新日期: 2023-09-18阅读: 848标签: 存储

离线储存应用程式资料已成为现代Web开发中的必要条件。内建的浏览器 localStorage 可以用作简单轻量资料的资料储存,但是在结构化资料或储存大量资料方面却不足。

最重要的是,我们只能将字串资料储存在受XSS攻击的 localStorage 中,并且它没有提供很多查询资料的功能。

这就是IndexedDB的亮点。使用IndexedDB,我们可以在浏览器中建立结构化的数据库,将几乎所有内容储存在这些数据库中,并对资料执行各种型别的查询。

在本文中,我们将瞭解IndexedDB的全部含义,以及如何使用Dexie.js(用于IndexedDB的简约包装)处理Web应用程式中的离线资料储存。


IndexedDB如何工作

IndexedDB是用于浏览器的内建非关联式数据库。它使开发人员能够将资料持久储存在浏览器中,即使在离线时也可以无缝使用Web应用程式。使用IndexedDB时,您会经常看到两个术语:数据库储存和物件储存。让我们在下面进行探讨。


使用IndexedDB建立数据库

IndexedDB数据库对每个Web应用程式来说都是唯一的。这意味著一个应用程式只能从与自己执行在同一域或子域的 IndexedDB 数据库中存取资料。数据库是容纳物件储存的地方,而物件储存又包含储存的资料。要使用IndexedDB数据库,我们需要开启(或连线到)它们:

const initializeDb = indexedDB.open('name_of_database', version)

indexedDb.open() 方法中的 name_of_database 引数将用作正在建立的数据库的名称,而 version 引数是一个代表数据库版本的数位。

在IndexedDB中,我们使用物件储存来构建数据库的结构,并且每当要更新数据库结构时,都需要将版本升级到更高的值。这意味著,如果我们从版本1开始,则下次要更新数据库的结构时,我们需要将 indexedDb.open() 方法中的版本更改为2或更高版本。


使用IndexedDB建立物件储存

物件储存类似于关联式数据库(如PostgreSQL)中的表和档案数据库(如MongoDB)中的集合。要在IndexedDB中建立物件储存,我们需要从之前宣告的 initializeDb 变数中呼叫 onupgradeneeded() 方法:

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() 方法为数据库建立物件储存。每个物件储存的值代表了它的结构。例如,当在第一个物件储存中储存资料时,我们需要提供一个具有属性 name 和 price 的物件。++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进行离线储存,我们将从在终端中执行以下命令开始,以安装 dexie 和 dexie-react-hooks 软体包:

npm i -s dexie dexie-react-hooks

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

让我们将以下汇入语句新增到我们的 ./src/App.js 档案中。这将汇入 Dexie 和 useLiveQuery 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 物件储存将期待一个具有属性name、price和 itemHasBeenPurchased 的物件,而 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,否则更新为 false。db.[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 物件获取属性 id、name、price 和 itemHasBeenPurchased。然后,我们继续进行操作,并用数据库中的新动态值替换了以前的静态资料。

注意,我们还使用了 markAsPurchased 和 removeItemFromDb 方法作为相应按钮的单击事件侦听器。我们将在下一个程式码块中将 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-form 的 onsubmit 值。

为了测试我们的应用程式,我们可以返回到我们先前开启的React网页。请记住,您的React应用必须正在执行,如果不是,请在终端上执行命令 npm start。

我们还可以使用条件用Dexie查询我们的IndexedDB数据库。例如,如果我们要获取价格高于10美元的所有商品,则可以执行以下操作:

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


结束语

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

由于IndexedDB是浏览器原生的,从数据库中查询和检索资料比每次需要在应用中处理资料时都要传送伺服器端API请求要快得多,而且我们几乎可以在IndexedDB数据库中储存任何东西。

过去使用IndexedDB可能对浏览器的支援是一个大问题,但是现在所有主流浏览器都支援它。在Web应用中使用IndexedDB进行离线储存的诸多优势大于劣势,将Dexie.js与IndexedDB一起使用,使得Web开发变得前所未有的有趣。

更多关于Dexie.js库的介绍:点击进入

链接: https://fly63.com/article/detial/12522

js中cookie操作总结:cookie设置,读取,删除,判断是否存在

有时也用其复数形式 Cookies,指某些网站为了辨别用户身份,JavaScript对cookie的相关操作,设置cookie,读取cookie,删除cookie,判断cookie是否存在.......

HTML5新方法:前端存储localStorage的使用总汇

在HTML5中有一个localStorage的新特性,它主要用于本地存储使用,目的是为了解决了cookie存储空间小的问题。本文将讲解:localStorage特点、localStorage的兼容、localStorage的使用等

Token ,Cookie和Session的区别

在做接口测试时,经常会碰到请求参数为token的类型,但是可能大部分测试人员对token,cookie,session的区别还是一知半解

介绍web开发中实现会话跟踪的常用技术方法

由于http是无状态的协议,这种特性严重阻碍了客户端与服务器进行动态交互,为了弥补http的不足,目前实现会话跟踪的常用技术方法:cookie、session、url重写、隐藏input、ip地址。

关于网页本地存储的一些思考

localStorage 与 sessionStorage具体适用于什么样的业务场景?如何维护本地储存?如何进行版本控制?碰到禁止本地缓存的情况下怎么解决这个问题?

JS中原始值和引用值的储存方式

在ECMAscript中,变量可以存放两种类型的值,即原始值和引用值。原始变量及他们的值储存在栈中,当把一个原始变量传递给另一个原始变量时,是把一个栈房间的东西复制到另一个栈房间,且这两个原始变量互不影响。

前端数据保存_使用js开发数据库

前端很多时候还是需要保存一些数据的,这里的保存指的是长久的保存。以前的思想是把数据保存在cookie中,或者将key保存在cookie中,将其他数据保存在服务器上。想要一种能够长久的保存在本地的数据,类似数据库或者类似web sql。

Cookie防篡改机制_cookie怎么防止被篡改/伪造

因为Cookie是存储在客户端,用户可以随意修改。所以存在一定的安全隐患,服务器为每个Cookie项生成签名。如果用户篡改Cookie,则与签名无法对应上。以此,来判断数据是否被篡改。

在前后端分离的项目中,ajax跨域请求怎样附带cookie

后台保存用户信息通常使用的session和cookie结合的方法,而在前端的实际情况中,跨域产生的ajax是无法携带cookie信息的,这样导致了session和cookie的用户信息储存模式受到影响,该怎样去解决这样一个问题呢

localStorage设置过期时间

我们都知道localStorage不主动删除,永远不会销毁,那么如何设置localStorage的过期时间呢?使用场景: 1.利用本地数据,减少网络传输 ,2.弱网络环境下,高延迟,低带宽,尽量把数据本地化

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!