大多数应用程序基本都需要保存一些在应用重新加载时需要的数据。我们经常使用用户设备上的本地存储来实现。当使用Ionic/Angular的时候,我们可以简单的使用Ionic内置的Storage API,并不需要知道背后的原理——Ionic会自动地选择最合适的存储方式。
再说一次,在Vue里我们没有现成的东西可以用,是安装一个库还是自己搭建解决方案全决定于我们自己。
在这篇教程,我们将扩展上篇教程里的Reddit应用,允许我们可以切换多个不同的subreddits方法。我们会存储用户的选择,当重新进入应用时会使用保存的数据。
本教程紧跟前面的教程。如果你想一步步走, 你应该先完成之前的教程。不过, 您可以轻松地将本教程中的概念应用于任何应用程序, 因此如果您不想完成前面的教程也无大碍。
进入代码之前我们先讨论一点理论知识。我们会在ionic-angular库的Ionic Storage Module代码的基础上进行。具体点说,我们会参考 the Storage class的代码。
Ionic使用 localForage来和存储后台交互。通过使用 localForage我们可以有一些外部的API进行交互,不用管具体使用了哪个技术:Native SQLite storage, IndexedDB, WebSQL或者是简单的浏览器的local storage。
Ionic的存储模块封装了localForage ,而且根据可用的选择,去自动的设置合适的方法。这意味着如果你使用Cordova搭建的应用而且安装了SQLite 插件,那这个就会用于存储数据。如果SQLite 不可用它会回头使用IndexedDB 或者 WebSQL,如果这些都不可用那么local storage API就是最后的选择 。
我们想在Ionic/Vue应用里模拟这种行为,所以来看看Ionic是怎么做的。我们会专注于 storage.ts里最关键的部分:
import LocalForage from 'localforage';
import CordovaSQLiteDriver from 'localforage-cordovasqlitedriver';
localForage 库必须安装和导入,如果我们想支持使用SQLite,还需要安装CordovaSQLiteDriver 。使用SQLite 的好处是我们不需要担心浏览器数据被系统清空。
constructor(config: StorageConfig) {
this._dbPromise = new Promise((resolve, reject) => {
let db: LocalForage;
const defaultConfig = getDefaultConfig();
const actualConfig = Object.assign(defaultConfig, config || {});
LocalForage.defineDriver(CordovaSQLiteDriver).then(() => {
db = LocalForage.createInstance(actualConfig);
})
.then(() => db.setDriver(this._getDriverOrder(actualConfig.driverOrder)))
.then(() => {
this._driver = db.driver();
resolve(db);
})
.catch(reason => reject(reason));
});
}
在构造函数constructor 里创建了一个实例化localForage的Promise。我们先要定义CordovaSQLiteDriver,一旦完成我们可以用构造对象创建一个Local Forage实例。
默认的Ionic Storage API 的构造是这样的:
{
name: '_ionicstorage',
storeName: '_ionickv',
driverOrder: ['sqlite', 'indexeddb', 'websql', 'localstorage']
};
这个配置最重要的部分就是drivers,这决定了存储机制的性能。在这个例子里,sqlite是首选,localstorage 是最后的选择。为了确保这些驱动命名正确,用到了下面这个方法:
_getDriverOrder(driverOrder) {
return driverOrder.map((driver) => {
switch (driver) {
case 'sqlite':
return CordovaSQLiteDriver._driver;
case 'indexeddb':
return LocalForage.INDEXEDDB;
case 'websql':
return LocalForage.WEBSQL;
case 'localstorage':
return LocalForage.LOCALSTORAGE;
}
});
}
这个方法会映射driver order数组来使用由localForage提供的合适的值。如果你不熟悉数组的map方法,你可能会对这个视频感兴趣。基本概念就是map操作会对数组里的每个元素进行一些转换。在这个例子里,我们会使用由localForage定义的实际的名称来替换数组里的值。
剩下的API基本都是一些围绕着localForage 的方法的简单封装:
get(key: string): Promise<any> {
return this._dbPromise.then(db => db.getItem(key));
}
set(key: string, value: any): Promise<any> {
return this._dbPromise.then(db => db.setItem(key, value));
}
所有Ionic存储模块在做的事情就是通过检查dbPromise 的类成员,来确保存储已经完成了正确的实例化。一旦promise完成,getItem 和setItem 方法就会被用来在存储空间里设置(或者销毁)值。
我们可以直接在我们的组件里使用localForage 接口,但是如果我们想做的像Ionic的Staorage API那样智能,把它独立到它自己的服务里会更有意义。我们将使用刚才学习到的那些概念,用它们在Ionic/Vue应用里来创建一个Storage服务。
首先,我们需要安装必要的依赖库:
npm install localforage --save
npm install localforage-cordovasqlitedriver --save
创建src/services/storage.js文件:
import LocalForage from 'localforage';
import CordovaSQLiteDriver from 'localforage-cordovasqlitedriver';
export default class Storage {
dbPromise;
constructor(){
this.dbPromise = new Promise((resolve, reject) => {
let db;
let config = {
name: '_vuestorage',
storeName: '_vuekv',
driverOrder: ['sqlite', 'indexeddb', 'websql', 'localstorage']
}
LocalForage.defineDriver(CordovaSQLiteDriver).then(() => {
db = LocalForage.createInstance(config);
})
.then(() => db.setDriver(this.getDriverOrder(config.driverOrder)))
.then(() => {
resolve(db);
})
.catch(reason => reject(reason));
});
}
ready(){
return this.dbPromise;
}
getDriverOrder(driverOrder){
return driverOrder.map((driver) => {
switch(driver){
case 'sqlite':
return CordovaSQLiteDriver._driver;
case 'indexeddb':
return LocalForage.INDEXEDDB;
case 'websql':
return LocalForage.WEBSQL;
case 'localstorage':
return LocalForage.LOCALSTORAGE;
}
});
}
get(key){
return this.dbPromise.then(db => db.getItem(key));
}
set(key, value){
return this.dbPromise.then(db => db.setItem(key, value));
}
remove(key){
return this.dbPromise.then(db => db.removeItem(key));
}
clear(){
return this.dbPromise.then(db => db.clear());
}
}
这只是Ionic Storage API 相同代码的简化版——它或多或少地以同样的方式起作用。为了看起来更友好我留下了一些功能没封装,但是你没有理由不去实现剩下的那些功能。
现在,我们要做的是使用我们的服务。保存数据很简单:
storage.set('something', 'somevalue');
获取数据:
storage.get('something').then((value) => {
console.log(value);
});
修改 src/components/HelloWorld.vue如下:
<template>
<ion-app>
<ion-header>
<ion-navbar>
<ion-title>REDDIT!</ion-title>
</ion-navbar>
</ion-header>
<ion-content>
<ion-button @click="switchSubreddit('funny')">Funny</ion-button>
<ion-button @click="switchSubreddit('gifs')">Gifs</ion-button>
<ion-button @click="switchSubreddit('worldnews')">Worldnews</ion-button>
<ion-list>
<ion-item v-for="post in posts" v-bind:key="post.data.id">
{{post.data.title}}
</ion-item>
</ion-list>
</ion-content>
</ion-app>
</template>
<script>
import RedditService from '../services/reddit';
import Storage from '../services/storage';
const storage = new Storage();
export default {
name: 'HelloWorld',
data () {
return {
posts: []
}
},
created() {
storage.get('subreddit').then((value) => {
if(value === null){
value = 'gifs';
}
RedditService.getPosts(value).then(response => {
this.posts = response.body.data.children;
});
});
},
methods: {
switchSubreddit(subreddit){
storage.set('subreddit', subreddit);
RedditService.getPosts(subreddit).then(response => {
this.posts = response.body.data.children;
});
}
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
h1, h2 {
font-weight: normal;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: inline-block;
margin: 0 10px;
}
a {
color: #42b983;
}
</style>
我们引入了刚创建的storage服务,我们首先在created里调用了get方法。created 方法会在组件已创建就执行,所以它会立即检查storage里subreddit存不存在,如果存在,subreddit值会被用于调用API,如果不存在,默认的使用gifs代替。
我们也设置了三个按钮以不同的值触发switchSubreddit 。这个方法会保存新的值到storage,然后调用getPosts。下次应用重启的时候,保存在Storage的值会代替默认的gifs 被使用。
备注:如果你没有看之前的教程,你可能不知道如何使用这个模板的组件。
没有做太多额外的工作,我们就创建了自己的storage服务,和Ionic/Angular应用的存储机制一样的便利和有用。