返回博客首页
← 所有文章

NativeScript 应用程序中的客户端存储

2019 年 4 月 16 日 — 作者:Raymond Camden

在为一个新的 NativeScript-Vue 演示(我想创建并撰写)制定大纲时,我发现 NativeScript 开发的另一个方面,我以前没有探索过——客户端存储。我对 Web 应用程序中客户端数据存储的各种方法非常熟悉,但我还没有研究 NativeScript 支持的内容。在本文中,我将讨论您可以做什么,展示一些示例,并与它们如何与标准 Web API 相关进行比较。当然,也会有一个猫的演示。

Web 上的存储

从现在开始,浏览器一直有能力在客户端存储数据,尽管存储多少和存储效果一直处于变化之中。总的来说,忽略我们不会提到的过时技术,Web 上的存储归结为两种核心技术。

  • Web 存储,更常见的是称为本地存储,是一个简单的“键值”系统,用于存储少量数据。当与大量数据一起使用时,它存在性能问题,但在我看来,这是对技术的错误使用,而不是规范本身的缺陷。它非常易于使用,并且非常适合用于持久化表单、购物车和用户偏好等任务。
  • IndexedDB不是一个简单的 API,但它更适合需要搜索和过滤的大量数据。总的来说,您可以将其与 MongoDB 和其他 NoSQL 类型解决方案进行比较。

这两者都拥有非常好的支持。根据 CanIUse 的数据,Web 存储 在浏览器中的支持率接近 97%,而 IndexedDB 的支持率接近 96%。显然,这些技术都没有取代“真正的”企业级存储服务器,但它们对于存储数据和通过允许浏览器在页面视图之间保存和持久化数据来提高性能都非常有用。

NativeScript 中的存储

所以,我已经明确表示,我从 Web 开发人员的角度进入 NativeScript,当我计划我的下一个演示应用程序时,我甚至没有考虑存储方面,因为我只是假设它基于我使用 Web 开发的经验而被默认提供。当我意识到我必须弄清楚要使用什么时,我很快就停了下来。幸运的是,我发现多种实用的数据存储方法,实际上与我现有的知识非常吻合。

使用 ApplicationSettings 简化操作

对于 NativeScript 应用程序来说,最简单的存储选项是 ApplicationSettings。如果您熟悉本地存储,那么这个功能将很容易上手。与本地存储类似,ApplicationSettings 用于通过键名存储简单值。该 API 支持

  • 设置值(当然)
  • 获取值(当然)
  • 检查值是否存在
  • 删除值
  • 删除所有内容
  • 以及提供对 ApplicationSettings 封装的底层原生 API 的快速访问,如果您需要更低级别的访问权限。

ApplicationSettings 的唯一真正的“复杂性”在于,存在用于处理布尔值、字符串和数值的不同 API。让我们来看一个实际应用中非常简单的示例。

以下应用程序将简单地跟踪它被打开的次数。这很简单,但实际上是一种您可能想要跟踪的酷指标。以下是初始模板。

<template>
    <Page class="page">
        <ActionBar title="Home" class="action-bar" />
        <ScrollView>
            <StackLayout class="home-panel">
                <!--Add your page content here-->
                <Label textWrap="true" :text="numberOfVisits" />
            </StackLayout>
        </ScrollView>
    </Page>
</template>

<script>
export default {
    data () {
        return {
            hits:0
        };
    },
    computed:{
        numberOfVisits() {
            return "You've been here " + this.hits + " times.";
        }
    }
}
</script>

我的视图只是一个绑定到我用来创建描述性消息的计算变量的Label元素。它使用hits,现在它只是 0。现在让我们添加 ApplicationSettings 支持。首先,导入库

const appSettings = require("tns-core-modules/application-settings");

接下来,我们可以添加created方法来执行组件设置时的代码

created() {
    this.hits = appSettings.getNumber("hits");
    if(!this.hits) this.hits = 0;
    appSettings.setNumber("hits", ++this.hits);
}

我们从appSettings中加载值开始。注意getNumber。正如我所说,ApplicationSettings支持加载布尔值、字符串和数值。所有 API 都相同,只是方法名称不同。因此,如果您想获取布尔值,您将使用getBoolean,显然对于字符串将使用getString

如果值没有定义,我将其设置为 0,这可能看起来很奇怪,因为我在data块中将其默认设置为 0,但在其他情况下它将为undefined。最后,我在将它递增 1 后将其存储回。

测试几次,您会看到每次保存代码并重新运行项目时,数字都会递增。如果您使用的是 NativeScript Playground,这将是自动的。我们可以使代码更简单一些。

与本地存储不同,ApplicationSettings支持为值不存在时提供默认值。这只需将默认值作为第二个参数提供即可

this.hits = appSettings.getNumber("hits",0);
appSettings.setNumber("hits", ++this.hits);

您可以查看和分叉这个简单项目的副本在这里

让我们考虑另一个更贴近实际的例子。想象一个允许用户自定义应用程序配色方案的应用程序。一种简单的方法是更改顶部的ActionBar的颜色。要启用此功能,我们首先可以将颜色绑定到一个值

<ActionBar title="Home" class="action-bar" :color="color" />

接下来,我们可以添加一个简单的字段让用户编辑这个值

<Label text="Enter RGB Color" />
<TextField v-model="color" hint="Try #00ff00" />

最后,以下是处理读取当前值和在更改时更新值的代码。

export default {
    data() {
        return {
            color:"#ff0000"
        };
    },
    created() {
        this.color = appSettings.getString("color","#ff0000");
    },
    watch: {
        color(val) {
            appSettings.setString("color", val);
        }
    }
};

现在,当应用程序加载时,颜色将从本地设置中读取,用户可以随意更改它。

您可以在这里玩这个项目。

要详细了解 ApplicationSettings,请务必阅读文档,并记住,与所有客户端数据一样,它绝对不应该用于敏感数据!

在我们继续之前,请知道有一个 NativeScript 插件添加了 LocalStorage 和 SessionStorage API:nativescript-localstorage。如果您只是想使用您已经熟悉的 API,这个插件可能会很方便。另一个原因是尝试合并需要该 API 的另一个库。

使用 CouchDB 复杂化

好吧,“复杂”不一定是最好的词,但对于更复杂的存储需求,一个很好的解决方案是 Osei Fortune 提供的 CouchDB 插件。该插件使存储任何类型的临时数据变得容易,同时还提供检索、更新、删除甚至搜索和排序这些数据的能力。不仅如此,您还可以设置 NativeScript 应用程序与远程 CouchDB 服务器之间的同步。

首先,您将通过tns plugin add nativescript-couchbase-plugin将插件添加到您的应用程序中。此插件无法在 Playground 上运行,因此您需要在本地工作才能进行测试。

接下来,您导入代码并创建一个数据库对象

import { Couchbase } from 'nativescript-couchbase-plugin';
const database = new Couchbase('my-database');

在上面的代码中,my-database是 CouchDB 数据库的名称,可以是任何对您的应用程序有意义的名称。让我们来看一个简单示例,它只处理随机但持久的数据。

screen shot of app

在上面的屏幕截图中,您可以看到猫的列表。列表下方是一个添加新猫的按钮。在一个更好的应用程序中,我会使用表单让您输入有关猫的详细信息,但为了简单起见,我使用了一些随机化来命名(和年龄)猫。现在让我们看一下代码。

<template>
    <Page class="page">
        <ActionBar class="action-bar">
            <Label class="action-bar-title" text="Home"></Label>
        </ActionBar>

        <StackLayout>
            <ListView for="cat in cats">
                <v-template>
                    <Label :text="cat.name"></Label>
                </v-template>
            </ListView>
            <Button @tap="addCat" text="Add Cat"></Button>
        </StackLayout>
    </Page>
</template>

<script>
import { Couchbase } from 'nativescript-couchbase-plugin';
const database = new Couchbase('cat-db');

// function to make some mock data
function getRandomInt(min, max) {
    return Math.floor(Math.random() * (max - min + 1)) + min;
}

function randomName() {
    var initialParts = ["Fluffy","Scruffy","King","Queen","Emperor","Lord","Hairy","Smelly","Most Exalted Knight","Crazy","Silly","Dumb","Brave","Sir","Fatty"];
    var lastParts = ["Sam","Smoe","Elvira","Jacob","Lynn","Fufflepants the III","Squarehead","Redshirt","Titan","Kitten Zombie","Dumpster Fire","Butterfly Wings","Unicorn Rider"];
    return initialParts[getRandomInt(0, initialParts.length - 1)] + ' ' + lastParts[getRandomInt(0, lastParts.length - 1)];
}

function getCats() {
    return database.query({
        select: [], // Leave empty to query for all
        order: [{ property: 'name', direction: 'asc' }]
    });
}

export default {
    data() {
        return {
            cats:[]
        }
    },
    created() {

        this.cats = getCats();

    },
    methods: {
        addCat() {
            let documentId = database.createDocument({
                "name": randomName(),
                "age": getRandomInt(1,10)
            });
            this.cats = getCats();
        }
    }
};
</script>

UI 处理遍历我的猫数据,显示名称。我的猫有名字和年龄,但显然您不必显示数据的每个部分。底部的按钮处理执行我的逻辑以添加新猫。

在应用程序逻辑内部,您可以看到我打开 CouchDB 数据库的位置。我有一个实用程序函数getCats,它使用数据库对象的query方法。我在这里没有进行任何过滤,只是排序,但您绝对可以按照自己的需要对数据进行“切片和切块”。

最后,addCat使用createDocument API 添加猫。名字和年龄都是随机值,但正如我上面所说,可以基于表单字段而不是随机值。

基本上就是这样,但请务必查看 插件文档以获取更多示例,包括我上面提到的同步以及如何使用事务。

其他选项?

除了我上面列出的内容外,您还有其他选项可用。一种是使用 SQLite,如果您更熟悉基于 SQL 的数据库,这种选项可能比 CouchDB 更受欢迎。

您还可以使用设备上的 文件系统。我可能不会将其用于“抽象数据”,而是将其用于存储图片和音频文件等内容。

与往常一样,我很想看看您做了什么。请在下方留言与我分享您的示例以及对您来说哪些有效(或无效)!