返回博客主页
← 所有文章

在您的 NativeScript-Vue 应用中使用 Vuex

2019 年 2 月 5 日 — 作者 Raymond Camden

恭喜您 - 您已决定开始构建原生移动应用程序,并希望使用 Vue.js 作为您的框架。太棒了!现在您可能想知道 - "纯 Web 方式" Vue 开发的哪些方面可以移植到您的 NativeScript 进程中?虽然它不一定是逐一的转换,但绝对可以移植的一个方面是(通常)对像 Vuex 这样的工具的需求。

在本文中,我将演示如何在 NativeScript-Vue 应用程序中添加 Vuex,但更好的是,我将对 Vuex 进行基本介绍,并描述为什么您可能要使用它。当我刚开始学习 Vue 的时候,这个“什么时候需要它”让我特别困惑,所以我会尽力把它讲清楚(就像有点干净的泥巴!),说明您什么时候想在您的应用程序中使用它。准备好了吗?让我们开始吧!

Vuex 到底是什么?

Vuex 将自己定义为:

"Vuex 是一个专门为 Vue.js 应用程序设计的状态管理模式 + 库。它充当应用程序中所有组件的集中式存储,并通过规则确保状态只能以可预测的方式被改变。"

这可能一开始并不直观,尤其是在您在 NativeScript-Vue 之前使用 Vue 构建更简单的应用程序时,Vue 用于在简单的网页中添加简单的交互和其他更新。(有时我认为 Vue 是一个为现代开发者设计的超级 jQuery 构建。)

但是,一旦您开始构建稍微复杂一点的应用程序,或者包含多个组件的应用程序(这肯定是在 NativeScript-Vue 中会遇到的),您就会开始发现一个问题:在多个组件之间保持数据同步。用户记录可能是整个应用程序中都会使用的东西,并且让每个组件(可能每个页面)都始终如一地反映关于该用户的正确信息,通过 Vuex 会变得容易得多。

是的,使用 Vuex 需要多做一些工作,但您会发现它提供的功能在 NativeScript-Vue 中真的很闪耀。让我们从构建一个简单的示例开始,并展示 Vuex 如何发挥作用。

在我们开始这个第一个演示之前 - 我想指出,我将跳过 NativeScript。无论何时学习新东西,我都会尝试让我的测试用例尽可能简单。因此,对于这个第一个示例,我只会使用 CodePen。下一个示例将把它正确地集成到一个真正的 NativeScript 移动应用程序中。

在 Vue 中构建一艘宇宙飞船!

让我们在 Vue 中构建一艘宇宙飞船!好的,我尽量将文章控制在两千字以内,所以与其说是宇宙飞船,不如说是非常非常有限的宇宙飞船的控制系统,它有两个系统 - 引擎和武器。飞船作为一个整体具有一定量的能量。我将创建一个由各种组件组成的面板,允许您一次增加或减少一个系统的能量。将能量增加到一个系统(例如引擎)必然会减少武器的能量。以下是控制面板在行动中的截图。

control panel

在这个截图中,有一个主要的 Vue 应用程序和两个子组件,每个子组件对应一个控制。以下是布局代码。

<div id="app" v-cloak>

  <h2>Current Status</h2>
  <p>
    Engines are at {{engines}} and weapons are at {{weapons}}.
  </p>

  <engine-control></engine-control>  
  <weapon-control></weapon-control>

</div>

并不太复杂,对吧?所以让我们创建一个存储。

const shipStore = new Vuex.Store({
  state: {
    engines:50,
    weapons:50
  },
  mutations: {

    increaseEngines(state) {
        if(state.engines < 100) {
                state.engines++;
                state.weapons--;
        }
    },
    decreaseEngines(state) {
        if(state.engines > 0) {
                state.engines--;
                state.weapons++;
        }
    },
    increaseWeapons(state) {
        if(state.weapons < 100) {
                state.weapons++;
                state.engines--;
        }
    },
    decreaseWeapons(state) {
        if(state.weapons > 0) {
                state.weapons--;
                state.engines++;
        }
    }
  }
});

Vuex 存储可以包含很多不同的东西,但在这里我将从简单开始,只包含两个主要方面。state 值的工作方式与 Vue 应用程序的 data 部分很像。它表示您的存储跟踪的信息。在我的例子中,它只是引擎和武器的能量计数。我最初将这两个值都设置为 50,表示飞船总共 100 个“单位”(是什么不重要)。更高级的示例可以存储总值,然后以编程方式确定两个系统。

Mutation 是您更改存储中值的工具。您的 Vue 应用程序可以直接访问状态值,但通常您想避免这样做。以下是一个很好的理由。我们不希望系统的总能量低于 0 或高于 100。mutation 处理了这个问题。mutation 默认情况下会传递当前状态值,但也接受额外的参数。mutation 必须是同步的,但存储也通过 action 支持异步操作。但是,让我们保持简单。

所以总结一下 - 我定义了存储的初始状态,以及外部应用程序操纵这些数据的途径。

const app = new Vue({
  el:'#app',
  store:shipStore,
  computed:{
    engines() {
        return this.$store.state.engines;
    },
    weapons() {
        return this.$store.state.weapons;
    },
  }
})

这里有两点需要注意。存储被传递到 Vue 构造函数中。我不需要这样做,但这样做会使它自动通过 this.$store 可用于子组件。现在请注意 computed 中的两种方法。两者都访问 this.$store.state 来返回每个组件的值。如果你还记得布局,这就是这部分的工作原理。

<h2>Current Status</h2>
<p>
    Engines are at {{engines}} and weapons are at {{weapons}}.
</p>

如果你不想直接访问状态,或者如果你想在存储本身中执行类似 computed 的操作,那么你很幸运。Vuex 存储支持 getters 属性,其工作原理与 computed 一样。现在让我们看看其中一个组件(只有一个,因为它们几乎相同)。以下是引擎组件。

Vue.component('engine-control', {
  template:`
  <form>
    <fieldset>
    <legend>Engine Controls</legend>
    <button @click.prevent="increaseEngine">Increase Engine</button>
    <button @click.prevent="decreaseEngine">Decrease Engine</button>
    </fieldset>
  </form>
`,
  methods:{
    increaseEngine() {
    this.$store.commit('increaseEngines');
    },
    decreaseEngine() {
        this.$store.commit('decreaseEngines');
    }
  }
});

布局在这里并不重要,但请注意两种方法。在这两种情况下,我们使用 this.$store.commit 访问存储 mutation。请注意,我只传递了 mutation 的名称。我也可以传递其他数据。例如,我可能想添加一个“满能量!”选项,它将所有能量转移到一个系统。

您可以在这个 CodePen 上查看所有代码并运行完整的示例。

查看 Raymond Camden (@cfjedimaster) 在 CodePen 上的 Pen 宇宙飞船存储

显然这是一个微不足道的例子,存储可能有点过分,但是随着应用程序变得越来越复杂(因为它很少反过来),存储就成为与飞船相关数据的“真理”的中心位置。我的 Vue 应用程序可以更多地关注 UI 问题(在按钮点击时执行等等),而存储则专注于集中应用程序的“逻辑”。

NativeScript、Vue 和 Vuex

现在让我们看看我们刚才构建的应用程序的 NativeScript 版本。在这个演示中,我将使用 NativeScript Playground,这样您就可以使用 NativeScript Playground 预览应用程序自己运行这个示例。让我们看看最终结果,然后我们可以深入研究代码。这是初始页面。我使用 TabView 创建三个视图。

nativescript vue app - tabview

初始视图显示了两个系统的基本状态。点击一个单独的选项卡会提供一个仅针对该系统的报告,以及用于修改功率的控件。

nativescript vue app - modify power

那么我如何构建它呢?我从使用 Vue 模板创建一个新的 Playground 应用程序开始。为了添加 Vuex 支持,我在 资源管理器 中使用了 + 符号,并选择了 NPM 选项。

nativescript playground vuex

然后我在搜索栏中键入“vuex”,并选择最新版本。

nativescript playground vuex package

如果您使用的是 CLI,在创建项目时会提示您是否要添加 Vuex 支持。如果您决定稍后添加 Vuex 支持,您需要在终端中使用 npm install vuex --save。这在 文档 中有更详细的说明。如果您熟悉 Vue CLI,您知道它也支持添加插件,但您不想在 NativeScript Vue 应用程序中使用该选项。现在让我们看看代码。我做的第一件事是创建我的存储。与 CodePen 示例不同,这个存储在它自己的文件中。我创建了一个名为 store 的文件夹,然后是 shipStore.js。以下是内容。

import Vue from 'nativescript-vue';
import Vuex from '../vuex';
Vue.use(Vuex);

const shipStore = new Vuex.Store({
    state: {
            engines: 50,
            weapons: 50
    },
    mutations: {
        increaseEngines(state) {
            if (state.engines < 100) {
                state.engines++;
                state.weapons--;
            }
        },
        decreaseEngines(state) {
            if (state.engines > 0) {
                state.engines--;
                state.weapons++;
            }
        },
        increaseWeapons(state) {
            if (state.weapons < 100) {
                state.weapons++;
                state.engines--;
            }
        },
        decreaseWeapons(state) {
            if (state.weapons > 0) {
                state.weapons--;
                state.engines++;
            }
        }
    }
});

module.exports = shipStore;

在很大程度上,这与之前并没有太大区别,但请注意,我已经导入了 Vuex 并指定了 Vue 将使用它。说实话,直到我找到了 这个很棒的示例,我才意识到这一点。

我的下一个修改是对我的核心 app.js 文件,以便包含和注册存储。

import Vue from 'nativescript-vue';
import Vuex from './vuex';
Vue.use(Vuex);

import SpaceShip from './components/SpaceShip';

import shipStore from './store/shipstore';

// Uncommment the following to see NativeScript-Vue output logs
// Vue.config.silent = false;

new Vue({

    render: h => h('frame', [h(SpaceShip)]),
    store:shipStore

}).$start();

同样,这与之前的示例非常相似。通过将存储传递到我的 Vue 组件中,我的根应用程序和所有子组件都将拥有它。现在让我们看看那个根组件。

<template>
    <Page class="page">
        <ActionBar title="Spaceship Info" class="action-bar" />

        <TabView>
            <TabViewItem title="Main Report">
                <Label :text="status" />
            </TabViewItem>
            <TabViewItem title="Engines">
                <Engines />
            </TabViewItem>
            <TabViewItem title="Weapons">
                <Weapons />
            </TabViewItem>
        </TabView>
    </Page>
</template>

<script>
    import Engines from "./Engines";
    import Weapons from "./Weapons";

    export default {
        data() {
            return {};
        },
        components: {
            Engines, Weapons
        },
        computed: {
            status() {
                return `Engines are at ${this.engines} and weapons are at ${this.weapons}.`;
            },
            engines() {
                return this.$store.state.engines;
            },
            weapons() {
                return this.$store.state.weapons;
            }
        }
    };
</script>

在这个示例中,主要区别在于用于生成布局的标签。我使用的是 NativeScript 布局和 UI 组件,而不是 div 和其他 HTML 标签。但是,存储值的用法完全相同。现在让我们看看 Engine 组件。

<template>

    <StackLayout>
        <Label :text="status" />
        <Button text="Increase Engines" @tap="increaseEngine" />
        <Button text="Decrease Engines" @tap="decreaseEngine" />
    </StackLayout>

</template>

<script>
    export default {
        data() {
            return {};
        },
        computed: {
            status() {
                return `Engines currently at ${this.$store.state.engines}.`;
            }
        },
        methods: {
            increaseEngine() {
                this.$store.commit("increaseEngines");
            },
            decreaseEngine() {
                this.$store.commit("decreaseEngines");
            }
        }
    };
</script>

与之前一样,这里唯一的真正变化在于布局。我对存储的使用完全相同,这太棒了。对于那些熟悉 Vue 但对 NativeScript 还不太熟悉的人来说,这应该让您感到更加安心。我在这个组件中添加了一些文本,因为 NativeScript 版本使用选项卡一次显示一个视图。

您可以在这里找到 完整的源代码

但是等等 - 还有更多(是的,很多...)

这仅仅是使用 Vuex 的开始,但希望您已经开始了解采用这种方法的一些优势。在下一篇文章中,我将演示 Vuex 的更多功能,并构建一个更接近现实世界应用程序的东西。