恭喜您 - 您已决定开始构建原生移动应用程序,并希望使用 Vue.js 作为您的框架。太棒了!现在您可能想知道 - "纯 Web 方式" Vue 开发的哪些方面可以移植到您的 NativeScript 进程中?虽然它不一定是逐一的转换,但绝对可以移植的一个方面是(通常)对像 Vuex 这样的工具的需求。
在本文中,我将演示如何在 NativeScript-Vue 应用程序中添加 Vuex,但更好的是,我将对 Vuex 进行基本介绍,并描述为什么您可能要使用它。当我刚开始学习 Vue 的时候,这个“什么时候需要它”让我特别困惑,所以我会尽力把它讲清楚(就像有点干净的泥巴!),说明您什么时候想在您的应用程序中使用它。准备好了吗?让我们开始吧!
Vuex 将自己定义为:
"Vuex 是一个专门为 Vue.js 应用程序设计的状态管理模式 + 库。它充当应用程序中所有组件的集中式存储,并通过规则确保状态只能以可预测的方式被改变。"
这可能一开始并不直观,尤其是在您在 NativeScript-Vue 之前使用 Vue 构建更简单的应用程序时,Vue 用于在简单的网页中添加简单的交互和其他更新。(有时我认为 Vue 是一个为现代开发者设计的超级 jQuery 构建。)
但是,一旦您开始构建稍微复杂一点的应用程序,或者包含多个组件的应用程序(这肯定是在 NativeScript-Vue 中会遇到的),您就会开始发现一个问题:在多个组件之间保持数据同步。用户记录可能是整个应用程序中都会使用的东西,并且让每个组件(可能每个页面)都始终如一地反映关于该用户的正确信息,通过 Vuex 会变得容易得多。
是的,使用 Vuex 需要多做一些工作,但您会发现它提供的功能在 NativeScript-Vue 中真的很闪耀。让我们从构建一个简单的示例开始,并展示 Vuex 如何发挥作用。
在我们开始这个第一个演示之前 - 我想指出,我将跳过 NativeScript。无论何时学习新东西,我都会尝试让我的测试用例尽可能简单。因此,对于这个第一个示例,我只会使用 CodePen。下一个示例将把它正确地集成到一个真正的 NativeScript 移动应用程序中。
让我们在 Vue 中构建一艘宇宙飞船!好的,我尽量将文章控制在两千字以内,所以与其说是宇宙飞船,不如说是非常非常有限的宇宙飞船的控制系统,它有两个系统 - 引擎和武器。飞船作为一个整体具有一定量的能量。我将创建一个由各种组件组成的面板,允许您一次增加或减少一个系统的能量。将能量增加到一个系统(例如引擎)必然会减少武器的能量。以下是控制面板在行动中的截图。
在这个截图中,有一个主要的 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 版本。在这个演示中,我将使用 NativeScript Playground,这样您就可以使用 NativeScript Playground 预览应用程序自己运行这个示例。让我们看看最终结果,然后我们可以深入研究代码。这是初始页面。我使用 TabView
创建三个视图。
初始视图显示了两个系统的基本状态。点击一个单独的选项卡会提供一个仅针对该系统的报告,以及用于修改功率的控件。
那么我如何构建它呢?我从使用 Vue 模板创建一个新的 Playground 应用程序开始。为了添加 Vuex 支持,我在 资源管理器 中使用了 + 符号,并选择了 NPM 选项。
然后我在搜索栏中键入“vuex”,并选择最新版本。
如果您使用的是 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 的更多功能,并构建一个更接近现实世界应用程序的东西。