1.## indexedDB 前端缓存实战
- 业务场景:最近项目组接触一个H5的项目,里面有一个课程学习的功能,里面基本都是一些比较大的gif 图片或者是一些高清的 jpg /png 图片,这个时候如果每次通过用户学习的过程中点击下一步再去下载对应的图片,如果刚好下载的图片比较大就会导致用户交互体验极差的问题,所以当时就想在学习课程之前让用户提前下载资源并缓存在前端,这时需要在前端做一个缓存,前端缓存使用比较多的是localStorage/sessionStorage ,但是应该很明显不符合我们的业务场景(因为localStorage/sessionStorage 允许存储的空间最多5M左右)。
所以此时的我们想到了浏览器的IndexDB,和web SQL 但是web SQL 后面被废弃了不再维护,所以我们选择了IndexDB,下面来简单介绍一下它 - IndexDB 简介
IndexedDB和传统的关系型数据不同的是,它是一个key-value型的数据库。
value可以是复杂的结构体对象,key可以是对象的某些属性值也可以是其他的对象(包括二进制对象)。你可以使用对象中的任何属性做为index,以加快查找。
IndexedDB是自带transaction(事务)的,所有的数据库操作都会绑定到特定的事务上,并且这些事务是自动提交了,IndexedDB并不支持手动提交事务。
IndexedDB API大部分都是异步的,在使用异步方法的时候,API不会立马返回要查询的数据,而是返回一个callback。
异步API的本质是向数据库发送一个操作请求,当操作完成的时候,会收到一个DOM event,通过该event,我们会知道操作是否成功,并且获得操作的结果。
IndexedDB是一种 NoSQL 数据库,和关系型数据库不同的是,IndexedDB是面向对象的,它存储的是Javascript对象。
IndexedDB还有一个很重要的特点是其同源策略,每个源都会关联到不同的数据库集合,不同源是不允许访问其他源的数据库,从而保证了IndexedDB的安全性。
- IndexDB 使用
这里我们为了方便操作使用了官网推荐的一个操作IndexDB的框架 Dexie
具体Dexie 语法自行百度
下面直接讲使用:
首先创建一个 IndexDB.js 文件 来创建生成我们的 IndexDB 数据库
import Dexie from 'dexie'
// 创建 名为 smallclass_cache 的数据库
const cacheDb = new Dexie('smallclass_cache')
// version定义当前sql的版本号,后续修改表字段时可以增加版本号
// stores 创建表 参数为一个{}
// 此操作为 创建一个 imageCache 和 courseList 等 数据表, &表示 primaryKey(++表示自增字段)
cacheDb.version(1.2).stores({
imageCache: `++id,imgId,base64,status,courseId,createTime,updateTime`, // courseId 字段作为 与课程的关联字段 存储的是 对应的课程ID
imageCacheIOS: `++id,imgId,base64,status,courseId,createTime,updateTime`, // 兼容IOS
courseList: `++id,courseId,createTime,updateTime`,
testList: `++id,testId,createTime,updateTime`,
testImageCache: `++id,imgId,base64,status,testId,createTime,updateTime`,
testImageCacheIOS: `++id,imgId,base64,status,courseId,createTime,updateTime`
})
export default cacheDb
上述操作就是创建了一个名为:smallclass_cache 的数据库 并且库里面创建了上述6张表格(传入stores方法的对象内每个属性代表一张表)
- 下面简单写一下自己简单封装的增删改查(由于公司机密就不贴公司的业务代码了)
import cacheDb from './indexedDB'
export default class smallClassCache{
constructor(){
this.db = cacheDb
this.tb = cacheDb && cacheDb.testImageCache // 获取到需要操作的表格 (表格初始化)
}
async add(data){
return this.db.transaction('rw','testImageCache',async () =>{
const time = new Date().getTime()
try{
await this.tb.add({
...data,
status:0,
createTime: time,
updateTime: time
})
}catch(err){
console.error(`数据新增失败:${err}`)
}
})
}
// 删除匹配的数据
async delete(params){
return this.db.transaction('rw','testImageCache',async () =>{
try{
await this.tb.where(params).delete()
}catch(err){
console.error(`数据删除失败:${err}`)
}
})
}
// 批量删除
async batchDelete(params){
return this.db.transaction('rw','testImageCache',async () =>{
try{
await this.tb.where(params.filed).anyOf(params.values).delete()
}catch(err){
console.error(`数据批量删除失败:${err}`)
}
})
}
async update(data){
return this.db.transaction('rw','testImageCache',async () =>{
const updateTime = new Date().getTime()
try{
await this.tb.where('imgId').equals([data.imgId,data.testId]).modify({updateTime,...data})
}catch(err){
console.error(`数据更新失败:${err}`)
}
})
}
// 创建 或更新 如果存在则更新 不存在 则创建
async createAndUpdate(data){
return this.db.transaction('rw','testImageCache',async () =>{
try{
let {imgId,testId} = data
let oldData = await this.get({imgId,testId})
// 更新
if(oldData.length){
await this.update(data)
}else{ //创建
await this.add(data)
}
}catch(err){
console.error(`创建或更新失败:${err}`)
}
})
}
// 简单查询获取单条数据
async get(params){
let result = []
try{
result = await this.tb.where(params).toArray()
}catch(err){
console.error(`数据获取失败:${err}`)
}
return result
}
// 获取所有数据
async getAllData(){
let result = []
try{
result = await this.tb.toArray()
}catch(err){
console.error(`数据获取失败:${err}`)
}
return result
}
// 删除所有数据
async deleteAllData(){
try{
await this.tb.clear()
}catch(err){
console.log('删除失败',err)
}
}
}
这里需要注意的是尽可能的使用 try catch ,因为这样出错误了会比较好定位问题。
- 问题
由于我做的是移动端的内嵌H5,所以后面又出现了问题,再存储图片的过程中安卓存储base64的时候会出现报错的问题,而IOS 则没有这个问题,所以后续将安卓端存储图片的时候直接存储的是二进制流,而IOS存储的是Base64.