返回博客首页
← 所有文章

在 NativeScript-Vue 应用中使用 SQLite 和 Vuex 进行数据管理

2018 年 6 月 19 日 — 作者:Nic Raboy

我最近写了一个关于在使用 Vue.js JavaScript 框架的 NativeScript 应用程序中使用 Vuex 的教程。在这个名为《使用 Vuex 在 Vue.js NativeScript 应用程序中进行键值本地存储》的教程中,我们看到了几个使用 NativeScript 的应用程序设置模块进行键值存储的示例。

键值存储在某些场景下非常有用,但通常你会发现自己需要一些可以像 SQLite 一样进行查询的东西。

我们将利用 NativeScript + Vue.js 和 Vuex 进行状态管理,将我们之前的教程提升到一个新的水平,在使用 Android 和 iOS 构建的应用程序中使用原生 SQLite。

如果你还没有看过我的 上一篇文章,这不是成功阅读本文的必要条件,但在学习 Vuex 和 NativeScript 中的存储时,它还是很有价值的。

使用 Vue.js 和依赖项创建新的 NativeScript 应用程序

为了使事情简单易懂,我们将创建一个使用 NativeScript 和 Vue.js 的新项目。为此,请从命令行执行以下操作

vue init nativescript-vue/vue-cli-template vuex-project
cd vuex-project
npm install
npm run watch:ios

上述命令将使用 NativeScript 模板初始化一个新的 Vue.js 项目,安装立即的项目依赖项,然后在 iOS 上模拟它。但是,在使用 Vue CLI 创建的过程中,会问你一些问题。启用 Vuex 非常重要,但其他所有内容都可以保留为默认值。

创建 vuex-project 项目后,我们还没有完全完成。我们需要安装一个原生插件,它将为 Android 和 iOS 提供 SQLite 支持。

从命令行执行以下操作

npm install nativescript-sqlite --save

上述命令将为 NativeScript 安装 SQLite 插件

此时,我们可以开始向我们非常简单的应用程序添加逻辑。

使用 Vuex 开发 SQLite 的状态管理逻辑

如果你以前从未使用过 Vuex,它是一种从单个位置管理应用程序状态的方法。在使用生成器创建项目时,我觉得它在 Vuex 方面过于复杂了。因此,在添加我们自己的 Vuex 逻辑之前,我们将对项目进行一些修改。

在项目的 src/store/index.js 文件中,包含以下 JavaScript 代码

import Vue from 'nativescript-vue';
import Vuex from 'vuex';

const Sqlite = require("nativescript-sqlite");

Vue.use(Vuex);

const store = new Vuex.Store({
    state: {
        database: null,
        data: []
    },
    mutations: { },
    actions: { }
});

Vue.prototype.$store = store;

module.exports = store;

那么发生了什么变化?我没有在 src/store/index.js 文件中引用模块,而是直接在文件中包含了模块的内容。

现在让我们弄清楚我们想用 Vuex 实现什么。SQLite 插件是异步的,因此我们希望在我们的状态中维护一个打开的实例。我们还希望随时保持数据库数据加载并准备就绪,这可以在状态的 data 属性中发生。这些命名约定不是特定的,只要它们存在于 state 属性中即可。

任何时候我们想要更改状态变量,都需要在 mutation 中进行。但是,mutation 必须是同步的,因此我们也必须使用 action,action 可以是异步的。

那么我们的 mutation 可能是什么样子?请看下面的代码

mutations: {
    init(state, data) {
        state.database = data.database;
    },
    load(state, data) {
        state.data = [];
        for(var i = 0; i < data.data.length; i++) {
            state.data.push({
                firstname: data.data[i][0],
                lastname: data.data[i][1]
            });
        }
    },
    save(state, data) {
        state.data.push({
            firstname: data.data.firstname,
            lastname: data.data.lastname
        });
    },
}

我们在上面的代码中定义了三个不同的 mutation。我们有一个初始化操作,它将存储我们打开的数据库的状态。当我们希望加载数据库数据时,我们可以遍历查询结果并将其存储为对象数组。直接使用 SQLite 数据的原始形式并不那么令人愉快,但使用 JSON 非常容易。最后,我们有一种将数据保存到状态中的方法。

请记住,mutation 仅用于更改 state 变量。与数据库的实际交互将发生在 action 中

actions: {
    init(context) {
        (new Sqlite("my.db")).then(db => {
            db.execSQL("CREATE TABLE IF NOT EXISTS people (id INTEGER PRIMARY KEY AUTOINCREMENT, firstname TEXT, lastname TEXT)").then(id => {
                context.commit("init", { database: db });
            }, error => {
                console.log("CREATE TABLE ERROR", error);
            });
        }, error => {
            console.log("OPEN DB ERROR", error);
        });
    },
    insert(context, data) {
        context.state.database.execSQL("INSERT INTO people (firstname, lastname) VALUES (?, ?)", [data.firstname, data.lastname]).then(id => {
            context.commit("save", { data: data });
        }, error => {
            console.log("INSERT ERROR", error);
        });
    },
    query(context) {
        context.state.database.all("SELECT firstname, lastname FROM people", []).then(result => {
            context.commit("load", { data: result });
        }, error => {
            console.log("SELECT ERROR", error);
        });
    }
}

在 action 部分,我们有三个不同的 action,每个 action 最终都会调用我们的 mutation 之一。init action 将打开一个数据库并执行创建表的查询。创建表后,打开的数据库将通过 commit 方法传递给 mutation。插入信息时,数据将传递给 insert action 并执行查询。context.state.database 变量是我们 state 变量部分中打开的数据库。传递的数据用作我们查询的参数,当查询成功完成后,它将被提交到我们的 mutation,数据将保存在状态中。类似地,我们可以查询数据库中的数据。查询结果将被提交到我们的 mutation,以便它可以从组件中访问。

很多繁重的工作实际上是 SQL 语句。请记住,actions 用于异步代码,而 mutations 用于同步代码。

还有一件事必须做。

我们希望确保在应用程序打开时初始化我们的数据库。为此,请将以下行添加到 src/store/index.js 文件的底部

store.dispatch("init");

请注意,我们使用 dispatch 而不是 commit 来调用我们的 action。使用 Vuex 之后,我们可以在任何组件中使用它。

使用 Vue.js 组件创建用户体验

由于这是一个简单的应用程序,我们有两种简单的方法来创建我们的组件。我们可以继续使用项目的 src/components/Counter.vue 文件,或者我们可以将其重命名为更有意义的名称。由于我们存储的是人员数据,因此我已经将其重命名为 src/components/Person.vue。如果你决定重命名文件,请确保在 src/main.js 文件中更新它。

打开 src/components/Person.vue 文件并包含以下内容

<template></template>

<script>
    export default {
        data() {
            return {
                input: {
                    firstname: "",
                    lastname: ""
                }
            }
        },
        methods: {
            save() {
                this.$store.dispatch("insert", this.input);
            },
            load() {
                this.$store.dispatch("query");
            },
            clear() {
                this.input.firstname = "";
                this.input.lastname = "";
            }
        }
    };
</script>

我故意清空了 <template> 块,以便我们可以专注于 <script> 块。在 <script> 块中,我们初始化了一些变量,这些变量将绑定到我们的 HTML 表单。这些方法将绑定到一组按钮。当我们调用 save 方法时,我们将表单中的数据发送到 Vuex action 以存储到数据库中。同样,当我们调用 load 方法时,我们将调用获取数据的相应 action。最后,clear 函数将通过清除变量来清空我们的表单。这里没有什么太复杂的东西,因为 Vuex 为我们的 SQLite 数据库做了所有繁重的工作。

现在让我们看一下 <template>

<template>
    <Page class="page">
        <ActionBar class="action-bar" title="Person"></ActionBar>
        <GridLayout rows="auto, *" columns="*">
            <StackLayout class="form" row="0" col="0">
                <StackLayout class="input-field">
                    <Label text="First Name" class="label font-weight-bold m-b-5" />
                    <TextField class="input" v-model="input.firstname" />
                    <StackLayout class="hr-light"></StackLayout>
                </StackLayout>
                <StackLayout class="input-field">
                    <Label text="Last Name" class="label font-weight-bold m-b-5" />
                    <TextField class="input" v-model="input.lastname" />
                    <StackLayout class="hr-light"></StackLayout>
                </StackLayout>
                <GridLayout rows="auto, auto" columns="*, *">
                    <Button text="Save" @tap="save" class="btn btn-primary" row="0" col="0" />
                    <Button text="Load" @tap="load" class="btn btn-primary" row="0" col="1"  />
                    <Button text="Clear" @tap="clear" class="btn btn-primary" row="1" col="0" colSpan="2"  />
                </GridLayout>
            </StackLayout>
            <ListView for="person in $store.state.data" class="list-group" row="1" col="0">
                <v-template>
                    <StackLayout class="list-group-item">
                        <Label v-bind:text="person.firstname + ' ' + person.lastname" />
                    </StackLayout>
                </v-template>
            </ListView>
        </GridLayout>
    </Page>
</template>

我们页面的模板将是一个网格。网格的第一行将是我们的表单,第二行将是数据库中数据的列表。让我们先看看上面部分

<StackLayout class="form" row="0" col="0">
    <StackLayout class="input-field">
        <Label text="First Name" class="label font-weight-bold m-b-5" />
        <TextField class="input" v-model="input.firstname" />
        <StackLayout class="hr-light"></StackLayout>
    </StackLayout>
    <StackLayout class="input-field">
        <Label text="Last Name" class="label font-weight-bold m-b-5" />
        <TextField class="input" v-model="input.lastname" />
        <StackLayout class="hr-light"></StackLayout>
    </StackLayout>
    <GridLayout rows="auto, auto" columns="*, *">
        <Button text="Save" @tap="save" class="btn btn-primary" row="0" col="0" />
        <Button text="Load" @tap="load" class="btn btn-primary" row="0" col="1"  />
        <Button text="Clear" @tap="clear" class="btn btn-primary" row="1" col="0" colSpan="2"  />
    </GridLayout>
</StackLayout>

本质上,我们有一些输入字段和一些按钮。输入字段通过 v-model 属性绑定到我们初始化的变量,按钮通过 @tap 属性绑定到我们的方法。我们这里的大部分内容都是标准标记,没有真正的逻辑。

现在让我们看看后半部分

<ListView for="person in $store.state.data" class="list-group" row="1" col="0">
    <v-template>
        <StackLayout class="list-group-item">
            <Label v-bind:text="person.firstname + ' ' + person.lastname" />
        </StackLayout>
    </v-template>
</ListView>

我们有一个简单的列表,它从存储中的数据填充。请记住,我们不仅将所有数据存储在数据库中,还将其作为 JSON 存储。

结论

你刚刚了解了如何在 使用 NativeScript 的 Vue.js 应用程序中使用 SQLite。在使用 SQLite 时使用 Vuex 不是必需的,但它是处理数据时的建议方法。我们的示例很简单,因为它只使用了一个表和一些简单的查询,但只需付出很少的额外努力,它就可以变得更加复杂。如果 Vue.js 不是你的首选,我还写了一个名为《在 NativeScript Angular 移动应用程序中使用 SQLite》的教程,它关注的是相同概念,但使用的是 Angular。

如果你想使用键值存储而不是 SQLite,请务必查看我的 上一篇文章