返回博客首页
← 所有文章

在你的 NativeScript-Vue 应用中使用 Vuex - 现在还有猫!

2019 年 3 月 12 日 — 作者 Raymond Camden

在我之前的文章,我讨论了如何在你的 NativeScript 应用中使用Vuex。Vuex 是一个相当复杂的话题,我在上一个演示中只涵盖了它的一小部分,所以这篇文章将添加更多细节,并纠正一个明显的错误——没有包含任何猫。抱歉 - 我真的不知道我在想什么。

应用程序

在我们开始编写代码之前,让我们快速看一下应用程序。这是一个相当简单的单页应用程序,模拟了照顾一只猫。对于那些年龄大到记得的人来说,你可以把它想象成你手机上的电子宠物。打开应用程序时,你的猫很高兴。

happy cat

当然,随着时间的推移,你的猫对生活越来越不满意。

neutral cat

然后最终陷入绝望的深渊,因为你 - 再次 - 让你失望。

mad cat

正如你所见,应用程序分为三个部分。最上面是一个图片,代表了猫的一般状态(以及我艺术才能的缩影),以及更直接的数值状态(这主要用于测试,在“真实”游戏中会删除)和一个按钮,允许你喂猫。

当你喂猫时,它会慢慢变得更开心,但同时它也会变得更饿,因此更不开心。任何养过猫的人都会立即明白这种矛盾,这是一个简单的(猫)生活事实。所以让我们看一下它是如何构建的。

定义猫商店

我的应用程序分为两个部分 - 主组件用于显示和与猫进行交互,以及 Vuex 商店,它代表关于虚拟猫的数据,并允许进行交互。这个特定的 Vuex 演示将展示前一个演示中缺少的两个功能 - GetterAction

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 上。享受让猫开心的尝试。你会失败,但还是要玩得开心!