首页 > 其他分享 >客户端存储 — IndexedDB 实现分页查询

客户端存储 — IndexedDB 实现分页查询

时间:2024-10-29 09:47:49浏览次数:8  
标签:IndexedDB 存储 分页 db price request let event 客户端

前言

相信 IndexedDB 大家都有过了解,但是不一定每个人都有过实践,并且其中涉及到事务、游标等概念,会导致在初次使用时会有些不适应,那么本文会通过 IndexedDB 实现分页查询的形式进行实践,在开始之前,可以尝试思考一下浏览器的客户端存储你都了解哪些呢?

其实客户端存储分为下面几类:

  • cookie

  • Web Storage

    • sessionStorage
    • localStorage
  • IndexDB

cookie

cookies 的特点:

  • cookie 是与特定域进行绑定的
  • 所有 cookie 都会作为请求头部由浏览器发送给服务器
  • 大多数浏览器对 1个域 下所有 cookie 的限制是不超过 4096 Byte(即 4 KB),上下可以有 1 Byte 的误差

假如 cookie 中保存大量信息,那么可能会影响特定域下浏览器请求的性能,因为保存的 cookie 越大,请求完成的时间就越长,由于以上的这些限制, cookie 只适合保存服务器必要的信息.

Web Storage

Web Storage 的目的是为了解决客户端需要使用 cookie 存储一些不需要频繁发送回服务器的数据.

sessionStorage

sessionStorage 的特点:

  • 只存储会话数据,即数据只会存储到当前设置存储的 tab 页面被关闭或者是浏览器关闭
  • 只能存储字符串类型数据,不能存储结构化数据
  • 存储的数据不受页面刷新影响,可在浏览器崩溃并重启后恢复
  • 大多数浏览器限制 1个源 只能存储 5MB

localStorage

localStorage 的特点:

  • 存储的数据会一直保留,直到通过 JavaScript 删除或者用户清除浏览器缓存
  • 只能存储字符串类型数据,不能存储结构化数据
  • 存储的数据不受页面刷新影响,也不会因关闭窗口、标签页或重新启动浏览器而丢失
  • 大多数浏览器限制 1个源 只能存储 5MB

IndexDB

IndexedDB(Indexed Database API) ,是浏览器中 存储结构化数据 的一个方案,用于代替目前已废弃的 Web SQL Database API.

其中 API 的设计基本上是 异步的,大多数操作以请求的形式 异步执行,产生成功的结果或错误都有对应的 onerroronsuccess 事件处理程序来确定输出.

IndexedDB 的特点:

  • 能够存储结构化数据
  • IndexedDB 数据库是与 页面源 绑定的,不能跨域共享
  • 大小限制
    • Chrome 限制是每个源 5MB
    • Firefox 限制是每个源 50MB
    • Firefox(移动版) 限制每个源 5MB ,若超出则请求用户许可

从以上可以看出 IndexedDB 的限制其实 Web Storage 一样.

页面源(源)= 协议 + 域 + 端口

IndexedDB

数据库

IndexedDB 数据库是类似于 MySQL 或 Web SQL Database 的数据库,它使用对象存储数据而不是使用表格,并且属于 NoSQL 的风格.

建立数据库连接 —— indexedDB.open()

indexedDB.open(name, version) 其中 name 为数据库名称,version 为数据库版本,且版本只能为整数
通过 indexedDB.open(name, version) 使用数据库:

  • 如果给定名称的数据库 已存在,则会发送一个 打开请求
  • 如果 不存在,则会发送 创建打开 这个数据库的请求
  • 异步执行,返回 IDBRequest 实例,需要监听 onerroronsuccess 事件

如下将创建一个名为 test 的数据库:

image.png

let db, request, version = 1;
request = indexedDB.open("test", version);
request.onerror = (event) => alert(`Failed to open: ${event.target.errorCode}`);
request.onsuccess = (event) => {
  db = event.target.result;
};

对象存储 —— request.onupgradeneeded()

数据库 版本发生变化创建新数据库时 就会触发 onupgradeneeded 事件,此时需要指定或换修改 数据库模式 —— 包括数据库中的 对象存储 和对象 存储结构

如下将在 test 数据库下创建名为 productions 的对象存储模式

image.png

// 需要存储的对象
let production = {
  id: "20220128",
  name: "产品xxx",
}

// 指定对象存储的模式
request.onupgradeneeded = (event) => {
  const db = event.target.result;
  // 若存在就的存储结构则删除(可选)
  if (db.objectStoreNames.contains("productions")) {
    db.deleteObjectStore("productions");
  }
  // 创建对象存储,keyPath 表示应该用作键存储对象的属性名
  db.createObjectStore("productions", {
    keyPath: "id"
  });
};

事务 —— db.transaction()

IndexedDB 是基于事务的

创建对象存储之后,剩下的所有操作都是通过事务完成的,即若要 读写数据,都要通过事务 把所有修改操作进行组织.

创建事务

通过 let transaction = db.transaction() 方法来创建事务:

  • db.transaction()不指定参数 则对数据库中所有 对象存储只读 权限
  • db.transaction("productions") — 只加载特定对象存储的事务,只读 权限
  • db.transaction(["productions","xxx"], "readwrite") —— 加载多个对象存储的事务,且权限为 读写
transaction.onerror = (event) => {
  // 整个事务被取消
};
transaction.oncomplete = (event) => {
  // 整个事务成功完成
};

获取指定对象存储 —— transaction.objectStore()

通过 let store = transaction.objectStore("productions") 获取 productions 的对象存储,得到了对应的 对象存储 后,就可以使用以下方法:

  • store.get() — 获取数据
  • store.getAll() — 获取所有数据
  • store.add() — 添加数据
  • store.put() — 更新数据
  • store.delete() — 删除数据
  • store.clear() — 清除数据

写入对象

通过对象存储的引用 store 使用 store.add()store.put() 往对象存储中写入数据,它们都只接收一个要存储的对象作为参数,前提是这个对象必须包含存储对象中的 keyPath 属性对应的属性字段.

对象存储中已存在 同名的键 时:

  • store.add() 导致错误
  • store.put() 重写该对象
// productions 是一个产品数组
let request, requests = [];
for (let product of productions) {
  request = store.add(product);
  request.onerror = () => {
    // 处理错误
  };
  request.onsuccess = () => {
    // 处理成功
  };
  requests.push(request);
}

查询数据

键查询 —— store.get()

键指的就是 onupgradeneeded 中指定的 keyPath 属性所对应的值,比如在上面我们指定了 { keyPath: 'id' },那么在使用键查询时,就必须要使用已存储数据对应属性的对应值.

假设需要获取如上图中的数据:

let storeName = "productions"
// 先创建事务,通过事务获取指定的存储对象
let store = db.transaction(storeName, 'readwrite').objectStore(storeName)
// 通过存储对象的 get 方法以及对应的 keyPath 键的 id 属性值获取数据
let request = store.get(1643527674720);
request.onsuccess = (event) => {
   console.log(event.target.result);
}
request.onerror = (event) => {
   console.log(`query_key error with code:${event.target.errorCode}`);
}

索引查询 —— store.index(key).get(value)

对某些数据集可能需要为对象存储指定多个键,以便于后续查询数据时可以通过不同的键去获取,需要创建存储对象时使用 store.createIndex() 去创建索引,即 onupgradeneeded 事件处理程序中,如下创建了一个名为 price 的索引,后续查询数据时就可以使用这个所以名和对应的值进行查找

创建索引

request.onupgradeneeded = (event) => {
    db = event.target.result
    db.createObjectStore("productions", {
      keyPath: "id"
    }).createIndex("price", "price", {
      unique: false
    });
};

使用索引

  let storeName = "productions"
  let store = db.transaction(storeName, 'readwrite').objectStore(storeName)
  let request = store.index('price').get('72.57');
  request.onsuccess = (event) => {
    console.log(event.target.result);
  }
  request.onerror = (event) => {
    console.log(`query_index error with code:${event.target.errorCode}`);
  }

游标查询 —— store.openCursor() + cursor.continue()

从上面可以知道,使用事务通过一个已知键或者索引都可以取得一条记录,但如果想取得多条数据,那就需要在事务中创建一个游标.

let cursorRequest = store.openCursor();
cursorRequest.onsuccess = (event) => {
    let cursor = event.target.result;
    // 当游标 cursor 值不存在时,代表游标查找结束或出现异常,所以需要提前判断
    if (!cursor) return;
    console.log(cursor.value);
    // 在本次游标指向中,通过显示调用 continue 方法就可以将游标指向下一个数据
    cursor.continue();
}
cursorRequest.onerror = (event) => {
    console.log(`query_cursor error with code:${event.target.errorCode}`);
}

范围查找 —— index(索引) + cursor(游标) + IDBKeyRange(键范围)

IDBKeyRange(键范围)

  • IDBKeyRange.only(1643607832742) 得到对应键值的记录
  • IDBKeyRange.lowerBound(1643607832742, false) 得到对应键值的下限(开始位置),第二个参数:
    • false —— 从 1643607832742 记录开始,直到最后
    • true —— 从 1643607832742 的下一条记录开始,直到最后
  • IDBKeyRange.upperBound(1643607832742, false) 得到对应键值的上限(结束位置),第二个参数:
    • false —— 从头开始,到 1643607832742 记录为止
    • true —— 从头开始,到 1643607832742 的前一条记录为止
  • IDBKeyRange.bound() —— 同时指定上下限,接收四个参数:下限键上限键可选布尔值 表示是否 跳过下限可选布尔值 是否 跳过上限

使用这样的查找方式更加理想,因为这样的方式可以实现根据指定索引在指定范围内开始游标查询,实现比较合理的范围查找.

 let request = store.index('price').openCursor(IDBKeyRange.only(1643607832742));
request.onsuccess = (event) => {
    let cursor = event.target.result;
    if (!cursor) return;
    console.log(cursor.value);
    cursor.continue();
}
request.onerror = (event) => {
    console.log(`query_idnex_cursor error with code:${event.target.errorCode}`);
}

实现分页查询

模拟数据

要实现查询的前提是得先有数据,这里就通过 setTimeout 模拟异步请求获取数据:

const getData = () => {
  setTimeout(() => {
    const list = []
    const now = Date.now()
    
    for (let index = 0; index < 100; index++) {
      let price = Math.random() * 100
      price = price < 10 ? price + 10 : price

      list.push({
        id: now + index,
        name: `产品_${index + 1}`,
        price: price.toFixed(2)
      })
    }

    data.value = list
  })
}

然后通过循环调用 add() 方法将数写入到数据库中

const inser = (data = []) => {
  let objectStore = db.transaction(storeName, 'readwrite').objectStore(storeName)
  data.forEach(item => objectStore.add({ ...item }))
  console.log('数据插入成功!')
} 

分页效果

实现效果如下

主要代码

useIndexDB.js

let db = null
let storeName = 'test_db'
let lastRecords = []
let currSize = 0
let indexArr = ["name", "price"]

const getStoreByTransaction = () => db.transaction(storeName, 'readwrite').objectStore(storeName)

const query = (payload) => {
  return new Promise((resovle, reject) => {
    let records = []
    let { name, value, type, size = 10 } = payload || {}
    let objectStore = getStoreByTransaction()
    let request

    if (type) {
      request = type === 'next'
        ? objectStore.openCursor(IDBKeyRange.lowerBound(lastRecords[lastRecords.length - 1].id, type))
        : objectStore.openCursor(IDBKeyRange.upperBound(lastRecords[0].id, true), type)
    } else {
      request = name
        ? objectStore.index(name).openCursor(IDBKeyRange.only(value), type)
        : objectStore.openCursor()
    }
    lastRecords = []

    request.onsuccess = (event) => {
      let cursor = event.target.result
      if (!cursor || currSize >= size) {
        currSize = 0
        resovle(records)
        return
      }

      let lastItem = cursor.value
      lastRecords.push(lastItem)
      records.push(lastItem)
      currSize++

      if (!name) {
        cursor.continue()
      } else {
        currSize = 0
        resovle(records)
      }
    }

    request.onerror = (event) => {
      reject(`query_idnex_cursor error with code:${event.target.errorCode}`)
    }
  })
}

const inser = (data = []) => {
  let objectStore = db.transaction(storeName, 'readwrite').objectStore(storeName)
  data.forEach(item => objectStore.add({ ...item }))
  console.log('数据插入成功!')
}

const open = (payload) => {
  payload = payload || { name: 'test', version: 1, storeName: "productions" }
  return new Promise((resovle, reject) => {

    let request = indexedDB.open(payload.name, payload.version)
    storeName = payload.storeName

    request.onsuccess = (event) => {
      db = event.target.result
      resovle({
        query,
        inser,
        getStoreByTransaction,
        db
      })
      console.log(`Database connection successfully established`)
    }

    request.onerror = (event) => {
      reject(`Failed to open: ${event.target.errorCode}`)
    }

    request.onupgradeneeded = (event) => {
      db = event.target.result

      if (db.objectStoreNames.contains("productions")) {
        db.deleteObjectStore("productions")
      }

      let objectStore = db.createObjectStore("productions", {
        keyPath: "id"
      })

      indexArr.forEach((name) => {
        objectStore.createIndex(name, name, {
          unique: false
        })
      })

    }
  })
}

export default open

App.vue

<script setup lang="ts">
import { reactive, ref, onMounted, computed } from "vue"
import openDB from "./useIndexDB.js";

const data = ref([])
const queryResult = ref([])
const formItem = reactive({ price: '', name: '' })
let dbObj = null

onMounted(() => {
  openDB()
    .then(({ query, inser }) => {
      dbObj = { query, inser }
    })
})

const getData = () => {
  setTimeout(() => {
    const list = []
    const now = Date.now()

    for (let index = 0; index < 100; index++) {
      let price = Math.random() * 100
      price = price < 10 ? price + 10 : price

      list.push({
        id: now + index,
        name: `产品_${index + 1}`,
        price: price.toFixed(2)
      })
    }

    data.value = list
    console.log("数据获取成功!", data.value);
  })
}

const insertData = () => {
  dbObj.inser(data.value)
}

const payload = computed(() => {
  let obj = { value: formItem.name }
  if (formItem.name) {
    obj.name = 'name'
    obj.value = formItem.name
  }
  if (formItem.price) {
    obj.name = 'price'
    obj.value = formItem.price
  }

  return obj
})

const loadPage = (type) => {
  dbObj
    .query({ type })
    .then((result) => {
      console.log(result)
      queryResult.value = result.sort((a,b)=> a.id - b.id)
    })
}

const queryData = () => {
  dbObj
    .query({ ...payload.value })
    .then((result) => {
      console.log(result)
      queryResult.value = result.sort()
    })
}

</script>

<template>
  <div class="btn-box">
    <button @click="getData">获取数据</button>
    <button @click="insertData">插入数据</button>
  </div>
  <div class="box">
    <label for="name">
      名称:
      <input id="name" v-model="formItem.name" type="text" />
    </label>
    <label for="price">
      价格:
      <input id="price" v-model="formItem.price" type="text" />
    </label>
    <button class="btn" @click="queryData">查询</button>
  </div>
  <ul class="list" v-show="queryResult.length">
    <li v-for="item in queryResult" :key="item.id">{{item.name}} —— {{ item.price}}</li>
    <div>
      <button @click="loadPage('prev')">上一页</button>
      <button @click="loadPage('next')">下一页</button>
    </div>
  </ul>
</template>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
  transition: all 0.5s ease;
}
.box label {
  margin: 0 10px;
}
button {
  margin: 10px;
}
.list{
  width: 400px;
  margin: 10px auto;
}
</style>

标签:IndexedDB,存储,分页,db,price,request,let,event,客户端
From: https://blog.csdn.net/weixin_43822185/article/details/143321010

相关文章

  • 客户端和服务端的区别
    客户端和服务端是网络通信的核心元素,具有不同的职责和工作方式。它们的区别主要有:1.架构和职责;2.通信模型和流程;3.性能和资源要求;4.部署环境和维护;5.安全性和隐私;6.开发和测试。本文深入探讨它们在架构、性能要求、安全性、开发和维护方面的差异,帮助理解这两种网络实体的角色和重......
  • 基于node.js+vue基于Android的新闻移动客户端的设计与实现前(开题+程序+论文)计算机毕业
    本系统(程序+源码+数据库+调试部署+开发环境)带文档lw万字以上,文末可获取源码系统程序文件列表开题报告内容选题背景在当今数字化时代,新闻传播方式发生了巨大变革。关于新闻移动客户端的研究,现有研究主要以新闻内容的推送、展示效果以及用户界面设计等为主。然而专门针对基......
  • zlibrary镜像网页,zlibrary中文入口及客户端/app
    Z-library,被誉为全球范围内最为庞大的数字图书馆之一,其藏书量之丰富令人叹为观止,总计囊括了超过9,826,996册电子书及84,837,646篇学术期刊文章。这座庞大的知识宝库覆盖了从经典文学巨著到前沿理工学科,从人文艺术瑰宝到专业学术论文的广泛领域,几乎能够满足每一位求知者的阅读与学......
  • zlibrary地址最新,Z-library电脑/app客户端下载
    Z-Library是一家电子图书馆,被誉为全球最大的科学图书和学术文献免费资源之一。它创办于2009年,截至2022年10月1日,已收录超过1129万本图书和8483万篇学术文章。从各种知名文学著作,理工学科,人文艺术、到学术论文等应有尽有!支持PDF、epub、mobi等多种格式图书资源下载绝对是你找书的不......
  • zlibrary地址2024,Z-library电脑客户端/app下载
    zlibrary是一家电子图书馆,被誉为全球最大的科学图书和学术文献免费资源之一。它创办于2009年,截至2022年10月1日,已收录超过1129万本图书和8483万篇学术文章。从各种知名文学著作,理工学科,人文艺术、到学术论文等应有尽有!支持PDF、epub、mobi等多种格式图书资源下载绝对是你找书的......
  • RabbitMQ之Java客户端的使用
    一、前言RabbitMQ支持多种语言访问,本次介绍RabbitMQJavaClient的一些简单的api使用,如声明Exchange、Queue,发送消息,消费消息,一些高级api会在后面的文章中详细的说明。二、项目实战1.引入依赖<dependency><groupId>com.rabbitmq</groupId><artifa......
  • zlibrary数字图书馆最新网址及电脑客户端/app下载
    zlibrary数字图书馆Z-Library是一家电子图书馆,同时也是全球科学图书和学术文献最大的免费资源之一。它拥有庞大的数字档案库,包含超过1400万本书籍,涵盖了多个领域和语种,使得用户能够轻松获取所需的文学作品、学术资料等。此外,Z-Library还在全球42个国家设立了150多个实体书籍交流......
  • zlibrary镜像网页,zlibrary电脑客户端/app下载
    Z-Library,也被广泛称为BookFinder.Z-Library或简称Z-Lib,是一个广受欢迎的数字图书馆和电子书存储库。以下是对Z-Library的详细介绍:一、概述Z-Library自称为“世界上最大的免费电子书库”,拥有超过10,000,000+本电子书和84,000,000+篇文章供用户免费下载。这些资源涵盖了广泛的学......
  • WPF+MVVM案例实战(六)- 自定义分页控件实现
    文章目录1、项目准备2、功能实现1、分页控件DataPager实现2、分页控件数据模型与查询行为3、数据界面实现3、运行效果4、源代码获取1、项目准备打开项目Wpf_Examples,新建PageBarWindow.xaml界面、PageBarViewModel.cs,在用户控件库UserControlLib中创建用......
  • zlibrary网址是哪个及zlibrary电脑客户端/app下载
    Z-Library是一家电子图书馆,同时也是全球科学图书和学术文献最大的免费资源之一。以下是对Z-Library的详细介绍及使用步骤:一、Z-Library介绍资源规模:Z-Library的数字档案库包含超过1400万本书籍,涵盖了广泛的学科领域,使其成为互联网上最为庞大的图书馆之一。实体书籍交流:Z-Librar......