Blazor和Vue对比学习(进阶2.2.5):状态管理之持久化保存(3),LocalStorage和IndexedDB()

PS1:点击查看Blazor中C#和JS互操作

PS2:Vue中,可以直接使用LocalStorage和IndexedDB对象,本章节案例主要以Blazor的使用为主

一、Storage对象

1、浏览器内置的键值对存储。locallStorage理论上永久保存在浏览器中(除非主动清除),而SessionStorage只在当前会话中有效,标签页或浏览器关闭,自动清除。遵守同源限制(按协议、域名和端口隔离),同一站点的Storage,大小限制为5M,键值支持JSON格式。常用API如下所示(SessionStorage和LocalStorage用法一样): 

//①存储和更新数据
//存储名字为name值为lily的变量
localStorage.setItem("name","lily");
//可以用点(.)操作符,及[]的方式进行数据存储
localStorage.name = "lily";  


//②读取数据
//读取指定键数据
localStorage.getItem("name");
var name = localStorage.name;
//读取第一条数据
localStorage.key(0);
//遍历localStorage
for(var i=0; i<localStorage.length;i++){
    ......
}

 
//③删除数据
localStorage.removeItem("name");
localStorage.name = "";  


//④全部清除数据
localStorage.clear();

2、在Blazor中使用Storage 

//C#中调用JS的步骤:
//①注入IJSRuntime,inject IJSRuntime JS
//②无返回值,调用JS.InvokeVoidAsync("JS函数名",参数1,参数2...)
//③有返回值,调用JS.InvokeAsync<T>("JS函数名",参数1,参数2...)
//localStorage.setItem等,是JS内置函数,定义在window对象下,可以直接调用,不需要在JS文件中定义

@page "/"
@inject IJSRuntime JS

<h1>来自LocalStorage,键名为"name"的值:@name</h1>
<button @onclick="@(()=>SetLocalStorage("name","functionMC"))">设置localStorage值</button>
<button @onclick="@(()=>GetLocalStorage("name"))">读取localStorage值</button>

@code{
    private string? name;
    private async Task SetLocalStorage(string key,string value)
    {
        await JS.InvokeVoidAsync("localStorage.setItem", key, value);
    }
    private async Task GetLocalStorage(string key)
    {
        name = await JS.InvokeAsync<string>("localStorage.getItem", key);
    }
}

二、IndexedDB对象

IndexedDB是浏览器内置的非关系型数据库,以键值对的方式保存数据,理论上大小从250M起,是Web应用首推的本地数据库,相当于手机APP端的SQLite。支持异步、事务、索引,字段值支持二进制储存,如ArrayBuffer和Blob对象,和Storage一样,遵守同源限制。IndexedDB的使用,比Storage复杂,我们择其要点进行学习。

1、IndexedDB的常用内置对象,包括:

  • 数据库:IDBDatabase
  • 对象仓库:IDBObjectStore,相当于数据表
  • 索引: IDBIndex
  • 事务: IDBTransaction
  • 操作请求:IDBRequest
  • 指针: IDBCursor
  • 主键集合:IDBKeyRange

2、常用方法

(1)新建、打开或升级数据库 

//如果没有则新建数据库TestDB,且版本号为1
//如果已有数据库TestDB,且版本号一致,则打开数据库
//如果已有数据库TestDB,但版本号不一致,则升级数据库并打开
var request = window.indexedDB.open("TestDB", 1);
request.onerror = function(event) {
    console.log('数据库打开报错');
}
var db;
request.onsuccess = function(event) {
    db = request.result;
    console.log('数据库打开成功');
}
request.onupgradeneeded = function(event) {
    db = event.target.result;
    console.log("数据库升级成功");
}

(2)创建表、自增键、索引,关闭或删除数据库

//创建表在request.onupgradeneeded方法中进行
//创建数据表book,主键(keyPath)为id
request.onupgradeneeded = function(event) {
    db = event.target.result;
    var objectStore;
    if (!db.objectStoreNames.contains("book")) {
        objectStore = db.createObjectStore("book", {keyPath: "id"});
    }
}

//创建数据表book,并使用自增主键
request.onupgradeneeded = function(event) {
    db = event.target.result;
    var objectStore;
    if (!db.objectStoreNames.contains("book")) {
        objectStore = db.createObjectStore("book", {autoIncrement: true});
    }
}

//一般创建数据表后,可以创建索引
//注意索引是针对键值Value来建的,每条记录的Value都是JSON
objectStore.createIndex("name", "name", { unique: true });

(3)关闭或删除数据库

db.close();
window.indexedDB.deleteDatabase("book");

(4)CRUD操作

//新增操作==================================
//注意:Key值(主键)自增,插入记录中的id,name都是Value的JSON成员
function add(book) {
    var request = db.transaction(["book"], "readwrite") //新建事务,readonly(默认)/readwrite/versionchange 
        .objectStore("book") //拿到IDBObjectStore 对象
        .add({  // 插入记录
          id: book.id,
          name: book.name
    });
    request.onsuccess = function(event) {
        console.log("数据写入成功");
    }
    request.onerror = function(event) {
        console.log("数据写入失败");
    }
    request.onabort = function(event) {
        console.log("事务回滚");
    }
}


//读取数据==================================
//读取一条
function read() {
   var transaction = db.transaction(["book"]);
   var objectStore = transaction.objectStore("book");
   var request = objectStore.get(1); //传主键
   request.onerror = function(event) {
     console.log("事务失败");
   };
   request.onsuccess = function( event) {
      if (request.result) {
        console.log(request.result)
      } else {
        console.log("未获得数据记录");
      }
   };
}
//读取所有数据,需要使用游标
function readAll() {
    var objectStore = db.transaction(["book"]).objectStore("book");
    objectStore.openCursor().onsuccess = function(event) { 
    //也可以在索引上打开 objectStore.index("id").openCursor()
        var cursor = event.target.result;
        if (cursor) {
            console.log(cursor)
            cursor.continue();
        } else {
            console.log('没有更多数据了!');
        }
    }
}
//打开游标时也可以传参确定数据范围和游标方向
objectStore.openCursor(1) //打开主键为1
objectStore.openCursor(IDBKeyRange.bound(1, 10, false, true))//1到10
objectStore.openCursor(null, "prev")//游标方向往前,还有next
//可以直接通过游标更新或删除数据
cursor.update(updatedBook)//更新
cursor.delete()//删除


//更新数据==================================
function update() {
    var request = db.transaction(['book'],'readwrite').objectStore('book').put({
        id: 1,
        name: '书剑恩仇录2',
    });
    request.onsuccess = function(event) {
        console.log('数据更新成功');
    }
    request.onerror = function(event) {
        console.log('数据更新失败');
    }
}


//删除数据==================================
function remove() {
  var request = db.transaction(["book"], "readwrite")
    .objectStore('book')
    .delete(1);
  request.onsuccess = function (event) {
    console.log("数据删除成功");
  };
}

//清空数据
function clear() {
  var request = db.transaction(["book"], "readwrite")
    .objectStore("book")
    .clear();
  request.onsuccess = function (event) {
    console.log('数据清除成功');
  };
}

3、在Blazor中使用IndexedDB:使用IndexedDB的场景确实不多,像临时保存表单数据,使用LocalStorage就足够,键值也是支持JSON格式数据,而且C#调用JS,对象和JSON可能自动转换。数据量大的,也应该直接使用前后端分离的方案,所以下例简单说一下Blazor中使用IndexedDB的方法。特别注意案例中的异步说明!!! 

//=======================================
//步骤一:在wwwroot/js文件夹下,创建MyIndexedDB.js文件



//=======================================
//步骤二:引入MyIndexedDB.js,案例使用Server模式在_Host.cshtml文件下
<script src="_framework/blazor.server.js"></script>
<script src="~/js/MyIndexedDB.js"></script>



//=======================================
//步骤三:在MyIndexedDB.js中定义需要的方法
//下例仅演示创建数据表和增加数据
//注意IndexedDB的API均是异步执行,这是很大的一个坑

var MyIndexedDB = MyIndexedDB || {};

//创建数据表
MyIndexedDB.CreatObjectStore = function (dbName, version, objectStoreName) {
    //打开数据库
    var request = window.indexedDB.open(dbName, version);
    request.onerror = function (event) {
        console.log("数据库打开报错");
    }
    var db;
    request.onsuccess = function (event) {
        db = request.result;
        console.log("数据库打开成功");
    }
    //创建数据表
    request.onupgradeneeded = function (event) {
        db = event.target.result;
        console.log("数据库升级成功");
        if (!db.objectStoreNames.contains(objectStoreName)) {
            objectStore = db.createObjectStore(objectStoreName, { autoIncrement: true });
        }
    }
}

//添加数据
MyIndexedDB.AddBook = function (dbName, version, objectStoreName, object) {
    //打开数据库
    var db;
    var request = window.indexedDB.open(dbName, version);
    request.onerror = function (event) {
        console.log("数据库打开报错");
    }
    request.onsuccess = function (event) {
        db = request.result;
        console.log("数据库打开成功");
        //这里使用IndexedDB的巨坑之一,IndexedDB的方法均是异步,所以要在打开数据库连接的回调里执行CRUD操作
        var trans = db.transaction(objectStoreName, "readwrite");
        var objectStore = trans.objectStore(objectStoreName);
        var resultAdd = objectStore.add(object);
        resultAdd.onsuccess = function (event) {
            console.log("数据写入成功");
        }
        resultAdd.onerror = function (event) {
            console.log("数据写入失败");
        }
        resultAdd.onabort = function (event) {
            console.log("事务回滚");
        }
    }
}



//=======================================
//步骤四:Blazor中调用
@page "/"
@inject IJSRuntime JS

<button @onclick="@(()=>CreatObjectStore("TestDB",1,"Books"))">创建数据表Books</button>
<button @onclick="@(()=>AddBook("TestDB",1,"Books",new Book { Id = 1, Name = "无名" }))">添加一本书</button>

@code{
    //创建数据表
    private async Task CreatObjectStore(string dbName, int version, string objectStoreName)
    {
        await JS.InvokeVoidAsync("MyIndexedDB.CreatObjectStore", dbName, version, objectStoreName);
    }
    //新增数据
    private async Task AddBook(string dbName, int version, string objectStoreName,Book book)
    {   
        await JS.InvokeVoidAsync("MyIndexedDB.AddBook", dbName, version, objectStoreName, book);
    }
}
————————

PS1:点击查看Blazor中C#和JS互操作

PS2:Vue中,可以直接使用LocalStorage和IndexedDB对象,本章节案例主要以Blazor的使用为主

一、Storage对象

1、浏览器内置的键值对存储。locallStorage理论上永久保存在浏览器中(除非主动清除),而SessionStorage只在当前会话中有效,标签页或浏览器关闭,自动清除。遵守同源限制(按协议、域名和端口隔离),同一站点的Storage,大小限制为5M,键值支持JSON格式。常用API如下所示(SessionStorage和LocalStorage用法一样): 

//①存储和更新数据
//存储名字为name值为lily的变量
localStorage.setItem("name","lily");
//可以用点(.)操作符,及[]的方式进行数据存储
localStorage.name = "lily";  


//②读取数据
//读取指定键数据
localStorage.getItem("name");
var name = localStorage.name;
//读取第一条数据
localStorage.key(0);
//遍历localStorage
for(var i=0; i<localStorage.length;i++){
    ......
}

 
//③删除数据
localStorage.removeItem("name");
localStorage.name = "";  


//④全部清除数据
localStorage.clear();

2、在Blazor中使用Storage 

//C#中调用JS的步骤:
//①注入IJSRuntime,inject IJSRuntime JS
//②无返回值,调用JS.InvokeVoidAsync("JS函数名",参数1,参数2...)
//③有返回值,调用JS.InvokeAsync<T>("JS函数名",参数1,参数2...)
//localStorage.setItem等,是JS内置函数,定义在window对象下,可以直接调用,不需要在JS文件中定义

@page "/"
@inject IJSRuntime JS

<h1>来自LocalStorage,键名为"name"的值:@name</h1>
<button @onclick="@(()=>SetLocalStorage("name","functionMC"))">设置localStorage值</button>
<button @onclick="@(()=>GetLocalStorage("name"))">读取localStorage值</button>

@code{
    private string? name;
    private async Task SetLocalStorage(string key,string value)
    {
        await JS.InvokeVoidAsync("localStorage.setItem", key, value);
    }
    private async Task GetLocalStorage(string key)
    {
        name = await JS.InvokeAsync<string>("localStorage.getItem", key);
    }
}

二、IndexedDB对象

IndexedDB是浏览器内置的非关系型数据库,以键值对的方式保存数据,理论上大小从250M起,是Web应用首推的本地数据库,相当于手机APP端的SQLite。支持异步、事务、索引,字段值支持二进制储存,如ArrayBuffer和Blob对象,和Storage一样,遵守同源限制。IndexedDB的使用,比Storage复杂,我们择其要点进行学习。

1、IndexedDB的常用内置对象,包括:

  • 数据库:IDBDatabase
  • 对象仓库:IDBObjectStore,相当于数据表
  • 索引: IDBIndex
  • 事务: IDBTransaction
  • 操作请求:IDBRequest
  • 指针: IDBCursor
  • 主键集合:IDBKeyRange

2、常用方法

(1)新建、打开或升级数据库 

//如果没有则新建数据库TestDB,且版本号为1
//如果已有数据库TestDB,且版本号一致,则打开数据库
//如果已有数据库TestDB,但版本号不一致,则升级数据库并打开
var request = window.indexedDB.open("TestDB", 1);
request.onerror = function(event) {
    console.log('数据库打开报错');
}
var db;
request.onsuccess = function(event) {
    db = request.result;
    console.log('数据库打开成功');
}
request.onupgradeneeded = function(event) {
    db = event.target.result;
    console.log("数据库升级成功");
}

(2)创建表、自增键、索引,关闭或删除数据库

//创建表在request.onupgradeneeded方法中进行
//创建数据表book,主键(keyPath)为id
request.onupgradeneeded = function(event) {
    db = event.target.result;
    var objectStore;
    if (!db.objectStoreNames.contains("book")) {
        objectStore = db.createObjectStore("book", {keyPath: "id"});
    }
}

//创建数据表book,并使用自增主键
request.onupgradeneeded = function(event) {
    db = event.target.result;
    var objectStore;
    if (!db.objectStoreNames.contains("book")) {
        objectStore = db.createObjectStore("book", {autoIncrement: true});
    }
}

//一般创建数据表后,可以创建索引
//注意索引是针对键值Value来建的,每条记录的Value都是JSON
objectStore.createIndex("name", "name", { unique: true });

(3)关闭或删除数据库

db.close();
window.indexedDB.deleteDatabase("book");

(4)CRUD操作

//新增操作==================================
//注意:Key值(主键)自增,插入记录中的id,name都是Value的JSON成员
function add(book) {
    var request = db.transaction(["book"], "readwrite") //新建事务,readonly(默认)/readwrite/versionchange 
        .objectStore("book") //拿到IDBObjectStore 对象
        .add({  // 插入记录
          id: book.id,
          name: book.name
    });
    request.onsuccess = function(event) {
        console.log("数据写入成功");
    }
    request.onerror = function(event) {
        console.log("数据写入失败");
    }
    request.onabort = function(event) {
        console.log("事务回滚");
    }
}


//读取数据==================================
//读取一条
function read() {
   var transaction = db.transaction(["book"]);
   var objectStore = transaction.objectStore("book");
   var request = objectStore.get(1); //传主键
   request.onerror = function(event) {
     console.log("事务失败");
   };
   request.onsuccess = function( event) {
      if (request.result) {
        console.log(request.result)
      } else {
        console.log("未获得数据记录");
      }
   };
}
//读取所有数据,需要使用游标
function readAll() {
    var objectStore = db.transaction(["book"]).objectStore("book");
    objectStore.openCursor().onsuccess = function(event) { 
    //也可以在索引上打开 objectStore.index("id").openCursor()
        var cursor = event.target.result;
        if (cursor) {
            console.log(cursor)
            cursor.continue();
        } else {
            console.log('没有更多数据了!');
        }
    }
}
//打开游标时也可以传参确定数据范围和游标方向
objectStore.openCursor(1) //打开主键为1
objectStore.openCursor(IDBKeyRange.bound(1, 10, false, true))//1到10
objectStore.openCursor(null, "prev")//游标方向往前,还有next
//可以直接通过游标更新或删除数据
cursor.update(updatedBook)//更新
cursor.delete()//删除


//更新数据==================================
function update() {
    var request = db.transaction(['book'],'readwrite').objectStore('book').put({
        id: 1,
        name: '书剑恩仇录2',
    });
    request.onsuccess = function(event) {
        console.log('数据更新成功');
    }
    request.onerror = function(event) {
        console.log('数据更新失败');
    }
}


//删除数据==================================
function remove() {
  var request = db.transaction(["book"], "readwrite")
    .objectStore('book')
    .delete(1);
  request.onsuccess = function (event) {
    console.log("数据删除成功");
  };
}

//清空数据
function clear() {
  var request = db.transaction(["book"], "readwrite")
    .objectStore("book")
    .clear();
  request.onsuccess = function (event) {
    console.log('数据清除成功');
  };
}

3、在Blazor中使用IndexedDB:使用IndexedDB的场景确实不多,像临时保存表单数据,使用LocalStorage就足够,键值也是支持JSON格式数据,而且C#调用JS,对象和JSON可能自动转换。数据量大的,也应该直接使用前后端分离的方案,所以下例简单说一下Blazor中使用IndexedDB的方法。特别注意案例中的异步说明!!! 

//=======================================
//步骤一:在wwwroot/js文件夹下,创建MyIndexedDB.js文件



//=======================================
//步骤二:引入MyIndexedDB.js,案例使用Server模式在_Host.cshtml文件下
<script src="_framework/blazor.server.js"></script>
<script src="~/js/MyIndexedDB.js"></script>



//=======================================
//步骤三:在MyIndexedDB.js中定义需要的方法
//下例仅演示创建数据表和增加数据
//注意IndexedDB的API均是异步执行,这是很大的一个坑

var MyIndexedDB = MyIndexedDB || {};

//创建数据表
MyIndexedDB.CreatObjectStore = function (dbName, version, objectStoreName) {
    //打开数据库
    var request = window.indexedDB.open(dbName, version);
    request.onerror = function (event) {
        console.log("数据库打开报错");
    }
    var db;
    request.onsuccess = function (event) {
        db = request.result;
        console.log("数据库打开成功");
    }
    //创建数据表
    request.onupgradeneeded = function (event) {
        db = event.target.result;
        console.log("数据库升级成功");
        if (!db.objectStoreNames.contains(objectStoreName)) {
            objectStore = db.createObjectStore(objectStoreName, { autoIncrement: true });
        }
    }
}

//添加数据
MyIndexedDB.AddBook = function (dbName, version, objectStoreName, object) {
    //打开数据库
    var db;
    var request = window.indexedDB.open(dbName, version);
    request.onerror = function (event) {
        console.log("数据库打开报错");
    }
    request.onsuccess = function (event) {
        db = request.result;
        console.log("数据库打开成功");
        //这里使用IndexedDB的巨坑之一,IndexedDB的方法均是异步,所以要在打开数据库连接的回调里执行CRUD操作
        var trans = db.transaction(objectStoreName, "readwrite");
        var objectStore = trans.objectStore(objectStoreName);
        var resultAdd = objectStore.add(object);
        resultAdd.onsuccess = function (event) {
            console.log("数据写入成功");
        }
        resultAdd.onerror = function (event) {
            console.log("数据写入失败");
        }
        resultAdd.onabort = function (event) {
            console.log("事务回滚");
        }
    }
}



//=======================================
//步骤四:Blazor中调用
@page "/"
@inject IJSRuntime JS

<button @onclick="@(()=>CreatObjectStore("TestDB",1,"Books"))">创建数据表Books</button>
<button @onclick="@(()=>AddBook("TestDB",1,"Books",new Book { Id = 1, Name = "无名" }))">添加一本书</button>

@code{
    //创建数据表
    private async Task CreatObjectStore(string dbName, int version, string objectStoreName)
    {
        await JS.InvokeVoidAsync("MyIndexedDB.CreatObjectStore", dbName, version, objectStoreName);
    }
    //新增数据
    private async Task AddBook(string dbName, int version, string objectStoreName,Book book)
    {   
        await JS.InvokeVoidAsync("MyIndexedDB.AddBook", dbName, version, objectStoreName, book);
    }
}