在我之前的文章中,我讨论了如何在你的 NativeScript 应用中使用Vuex。Vuex 是一个相当复杂的话题,我在上一个演示中只涵盖了它的一小部分,所以这篇文章将添加更多细节,并纠正一个明显的错误——没有包含任何猫。抱歉 - 我真的不知道我在想什么。
在我们开始编写代码之前,让我们快速看一下应用程序。这是一个相当简单的单页应用程序,模拟了照顾一只猫。对于那些年龄大到记得的人来说,你可以把它想象成你手机上的电子宠物。打开应用程序时,你的猫很高兴。
当然,随着时间的推移,你的猫对生活越来越不满意。
然后最终陷入绝望的深渊,因为你 - 再次 - 让你失望。
正如你所见,应用程序分为三个部分。最上面是一个图片,代表了猫的一般状态(以及我艺术才能的缩影),以及更直接的数值状态(这主要用于测试,在“真实”游戏中会删除)和一个按钮,允许你喂猫。
当你喂猫时,它会慢慢变得更开心,但同时它也会变得更饿,因此更不开心。任何养过猫的人都会立即明白这种矛盾,这是一个简单的(猫)生活事实。所以让我们看一下它是如何构建的。
我的应用程序分为两个部分 - 主组件用于显示和与猫进行交互,以及 Vuex 商店,它代表关于虚拟猫的数据,并允许进行交互。这个特定的 Vuex 演示将展示前一个演示中缺少的两个功能 - Getter 和 Action。
Getter 与计算的 Vuex 属性非常相似。它们允许你定义需要某种逻辑的数据,你希望将这些逻辑封装起来。让我们先来看看猫的数据
state: {
hungriness: 0
},
是的,就是这样。虚拟猫的全部定义只有一个属性 - 它有多饿。但是,我们希望向应用程序公开猫有多开心,正如你所想,这是一个猫有多饿的函数。Getter 在 getters
块中定义
getters: {
happiness(state) {
// the more hungry, the less happy
return 100 - state.hungriness;
}
},
Getter 会传递商店的状态,并在状态发生变化时自动更新。在应用程序方面,这是通过 store.getters.happiness
来处理的。让我们看一个例子 - 图片和文本状态消息。
<Image :src="catImage" />
<Label textWrap="true" :text="status" />
这些都连接到计算属性
computed: {
catImage() {
// get the status, which is a % from 0 to 100
let status = this.$store.getters.happiness;
if (status > 90) {
return "~/images/cat_happy.png";
} else if (status > 60) {
return "~/images/cat_medium.png";
} else return "~/images/cat_sad.png";
},
status() {
return "status is " + this.$store.getters.happiness;
}
}
虽然使用的 Getter 很简单,但这里的好处是,随着猫变得越来越复杂,关于它有多开心的逻辑也可以更新。这是封装 101,本身并不新鲜,但你再次可以看到 Vuex 使这变得多么容易使用。现在让我们看看 Vuex 的另一个功能 - Action。
在上一篇文章中,我演示了Mutation 如何作为组件对商店进行更改的接口。Mutation 必须是同步的,这应该会引出一个问题 - 如何进行异步更改?这就是 Action 发挥作用的地方。
Action 不会直接更改商店中的状态 - 相反,它们也使用 Mutation。为了支持这一点,会向它们传递一个 context
对象以支持进行更改。Action 也可以接受任意参数。我们的猫定义了两个 Action。
第一个是“心跳”Action,它只是模拟时间的推移。这就是让猫随着时间的推移变得更饿(更不开心)的原因。
actions: {
// more stuff here...
heartbeat(context) {
console.log('heartbeat started');
setInterval(() => {
console.log('heartbeat running');
context.commit('hunger');
}, HB_SPEED * 1000);
}
}
我使用 setInterval
来初始化猫生命的定时重复。HB_SPEED
是一个定义其运行频率的常量变量,我在测试时调整了它,以找到猫应该有多烦人的“感觉”。请注意这一行
context.commit('hunger');
这就是 Action 如何通过 Mutation 请求更改的方式。在这种情况下,它只是让猫更饿
mutations: {
// more stuff here...
hunger(state) {
state.hungriness++;
if (state.hungriness > 100) state.hungriness = 100;
}
}
心跳调用从主 Vue 组件使用 mounted
事件发出
mounted() {
this.$store.dispatch("heartbeat");
},
Action 的名称作为第一个参数传递给商店,任何额外的参数都将简单地跟随在后面。现在让我们看看另一个 Action - 喂猫。
feed(context) {
console.log('feed started');
setTimeout(() => {
context.commit('feed');
}, FEED_SPEED * 1000);
},
feed
Action 在对名为 feed 的 Mutation 执行 commit 调用之前只是包装了一个 setTimeout
。为什么是 setTimeout
?仅仅是为了模拟当你喂猫时它不会立即感到满意。如果这看起来很武断和刻薄,那么你可能不了解猫。feed
Mutation 也是有点随机的
feed(state) {
let satisfaction = getRandomInt(1, 10);
console.log('statisfaction was set to ' + satisfaction);
state.hungriness -= satisfaction;
if (state.hungriness < 0) state.hungriness = 0;
},
在这种情况下,我认为即使你每次都喂猫同样的东西,也不一定意味着猫每次的反应都一样。还要注意这里的一段逻辑,以确保饥饿度永远不会低于 0。
基本上就是这样,所以让我们花点时间看看整体的商店
import Vue from 'nativescript-vue';
import Vuex from '../vuex';
Vue.use(Vuex);
//number of seconds for updating
const HB_SPEED = 2;
// when you feed the cat, it takes a while for it to be satisfied from it. cuz cats
const FEED_SPEED = 8;
const catStore = new Vuex.Store({
state: {
hungriness: 0
},
getters: {
happiness(state) {
// the more hungry, the less happy
return 100 - state.hungriness;
}
},
mutations: {
feed(state) {
let satisfaction = getRandomInt(1, 10);
console.log('statisfaction was set to ' + satisfaction);
state.hungriness -= satisfaction;
if (state.hungriness < 0) state.hungriness = 0;
},
hunger(state) {
state.hungriness++;
if (state.hungriness > 100) state.hungriness = 100;
}
},
actions: {
feed(context) {
console.log('feed started');
setTimeout(() => {
context.commit('feed');
}, FEED_SPEED * 1000);
},
heartbeat(context) {
console.log('heartbeat started');
setInterval(() => {
console.log('heartbeat running');
context.commit('hunger');
}, HB_SPEED * 1000);
}
}
});
// Credit: https://stackoverflow.com/a/1527820/52160
function getRandomInt(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min + 1)) + min;
}
module.exports = catStore;
本质上,商店代表整个猫,并定义集成,包括会更改状态的事物和读取状态的方式,以便任何组件都可以使用它。我们的应用程序只有一个主组件,但可以轻松扩展到更多。这是主组件
<template>
<Page class="page">
<ActionBar title="Cat Simulator 95 XP Ultimate Edition" class="action-bar" />
<ScrollView>
<StackLayout class="home-panel">
<Image :src="catImage" />
<Label textWrap="true" :text="status" />
<Button text="Feed the Cat" @tap="feedCat" />
</StackLayout>
</ScrollView>
</Page>
</template>
<script>
export default {
data() {
return {};
},
mounted() {
this.$store.dispatch("heartbeat");
},
methods: {
feedCat() {
this.$store.dispatch("feed");
}
},
computed: {
catImage() {
// get the status, which is a % from 0 to 100
let status = this.$store.getters.happiness;
if (status > 90) {
return "~/images/cat_happy.png";
} else if (status > 60) {
return "~/images/cat_medium.png";
} else return "~/images/cat_sad.png";
},
status() {
return "status is " + this.$store.getters.happiness;
}
}
};
</script>
<style scoped>
.home-panel {
vertical-align: center;
font-size: 20;
margin: 15;
}
</style>
如果你想自己试一试,整个代码库都在NativeScript Playground 上。享受让猫开心的尝试。你会失败,但还是要玩得开心!