使用 Vuex 模式与其它模式(例如 Redux)对比

我们将看到的最后一个场景是使用 Vuex 模式。在这种情况下,所有状态都保存在单个存储中。对状态的任何更新都会发送到此存储。 组件从存储中读取共享或全局状态。

Vuex 既是一种状态管理模式,也是 Vue.js 核心团队的库实现。该模式旨在缓解应用程序的不同部分共享全局状态时发现的问题。无法直接操作存储的状态。突变(Mutations)用于更新存储状态,并且由于存储状态是响应性的,Vuex 存储的任何消费者都会自动更新。

Vuex 从 JavaScript 状态管理领域的先前工作中汲取灵感,例如普及了单向数据流概念的 Flux 架构,以及 Flux 的单存储实现 Redux。

Vuex 不仅仅是另一个 Flux 实现。它是一个特定于 Vue.js 的状态管理库。因此,它可以利用 Vue.js 特定的东西(例如响应性)来提高更新的性能。下图显示了 props 和状态更新的层次结构:

image 2023 10 16 17 11 48 867
Figure 1. Figure 8.25: Vuex props and state updates hierarchy

为了更新全局状态,组件会触发存储中称为突变(mutation)的更新。store 知道如何处理此更新。它更新状态并通过 Vue.js 响应性相应地向下传播 props:

image 2023 10 16 17 13 38 636
Figure 2. Figure 8.26: Sequence diagram of a global state update with Vuex

我们可以使用 Vuex 扩展现有的应用程序。

首先,我们需要使用 yarn add vuexnpm install --save vuex 添加 vuex 模块。

接下来,我们需要使用 store.js 文件中的 Vue.use() 向 Vue 注册 Vuex:

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

最后,我们创建一个具有默认状态的 Vuex 存储。此状态包括我们在 store.js 文件中使用的相同 formData 对象。然后我们使用导出默认值(export default)导出它:

export default new Vuex.Store({
    state: {
        formData: {
            name: '',
            occupation: ''
        }
    },
})

最后,我们需要在 main.js 文件中向 Vue.js 的主应用程序实例注册我们的 store

// other imports
import store from './store'
// other imports and code
new Vue({
    render: h => h(App),
    store
}).$mount('#app')

最棒的是每个组件都有对 this.$store 下的 store 的引用。例如,要访问 formData,我们可以使用 this.$store.state.formData。使用此功能,我们可以使用单个计算属性替换 AppProfileDisplay.vue 文件的脚本部分中的事件总线订阅和本地状态更新:

<script>
export default {
    computed: {
        formData() {
            return this.$store.state.formData
        }
    }
}
</script>

为了触发状态更新,我们需要定义一些突变(mutations)。在这种情况下,我们需要 store.js 文件中的 profileUpdate。 突变(mutations)接收状态(state)(当前状态)和有效负载(payload)(存储 commit 有效负载)作为属性。

export default new Vuex.Store({
    // other store properties
    mutations: {
        profileUpdate(state, payload) {
            state.formData = {
                name: payload.name || '',
                occupation: payload.occupation || ''
            }
        }
    }
})

现在我们已经有了 profileUpdate 突变,我们可以更新 AppHeader.vue 文件中的 Reset 按钮以使用 Vuex $store.commit() 函数:

<script>
export default {
    methods: {
        clear() {
            this.$store.commit('profileUpdate', {})
        }
    }
}
</script>

我们还应该更新 AppProfileForm.vue 文件以提交到 $store 而不是发送到事件总线:

<script>
export default {
    // rest of component
    methods: {
        submitForm() {
            this.$store.commit('profileUpdate', {
                name: this.name,
                occupation: this.occupation
            })
        },
        // other methods
    }
}
</script>

该应用程序现在将支持更新姓名和职业(occupation):

image 2023 10 16 17 27 24 273
Figure 3. Figure 8.27: Application with AppProfileForm filled out and submitted

不幸的是,重置(Reset)按钮不会清除表单:

image 2023 10 16 17 28 11 579
Figure 4. Figure 8.28: Application with AppProfileForm not clearing on Reset button click

为了更有效地重置,我们将在 store.js 文件中添加 profileClear 突变:

export default new Vuex.Store({
    // other store properties
    mutations: {
        // other mutations
        profileClear(state) {
            state.formData = {
                name: '',
                occupation: ''
            }
        }
    }
})

我们将在 AppHeader.vue 文件中提交此操作而不是 profileUpdate。使用 profileClear 而不是带有空数据的 profileUpdate 可以清除我们的代码:

<script>
export default {
    methods: {
        clear() {
            this.$store.commit('profileClear')
        }
    }
}
</script>

最后,当 profileClear 提交到 AppProfileForm 文件中的存储时,我们需要订阅存储更改并重置本地状态:

<script>
export default {
    created() {
        this.$store.subscribe((mutation) => {
            if (mutation.type === 'profileClear') {
                this.resetProfileForm()
            }
        })
    },
    // other component properties
    methods: {
        // other methods
        resetProfileForm() {
            this.name = ''
            this.occupation = ''
        }
    }
}
</script>

现在应用程序的重置按钮可以与 Vuex 一起正常工作。我们的屏幕应显示如下:

image 2023 10 16 17 31 43 281
Figure 5. Figure 8.29: Application Reset button clearing both the form and the display

我们现在已经了解了如何使用 Vuex store 在我们的应用程序中存储全局状态。

练习 8.04:向配置文件生成器添加组织字段

在个人资料卡生成器中,除了个人的姓名和职业之外,了解他们的工作地点(换句话说,他们的组织)也很有用。

为此,我们将在 AppProfileForm 和 AppProfileDisplay 中添加一个组织(organization)字段。要访问本练习的代码文件,请参阅 https://packt.live/3lIHJGe

  1. 我们可以首先向 AppProfileForm 添加新的文本输入和标签:

    <template>
        <!-- rest of template -->
        <div class="flex flex-col mt-2">
            <label class="flex text-gray-800 mb-2"
            for="organization">Organization</label>
            <input
                id="occupation"
                type="text"
                name="organization"
                class="border-2 border-solid border-blue-200
                rounded px-2 py-1"
            />
        </div>
        <!-- rest of template -->
    </template>

    新字段如下所示:

    image 2023 10 16 17 43 09 611
    Figure 6. Figure 8.30: Application with a new Organization field
  2. 然后,我们可以将组织字段添加到 src/store.js 中的初始状态和突变,以便组织在 profileUpdate 期间初始化、设置,并在 profileClear 期间重置:

    // imports & Vuex setup
    export default new Vuex.Store({
        state: {
            formData: {
                // rest of formData fields
                organization: ''
            }
        },
        mutations: {
            profileUpdate(state, payload) {
                state.formData = {
                    // rest of formData fields
                    organization: payload.organization || '',
                }
            },
            profileClear(state) {
                state.formData = {
                    // rest of formData fields
                    organization: ''
                }
            }
        }
    })
  3. 我们需要使用 v-model 跟踪 src/components/AppProfileForm.vue 组件本地状态的组织(organization)并在 data() 函数中初始化它:

    <template>
        <!-- rest of template -->
        <div class="flex flex-col mt-2">
            <label class="flex text-gray-800 mb-2"
            for="organization">Organization</label>
            <input
                id="occupation"
                type="text"
                name="organization"
                v-model="organization"
                class="border-2 border-solid border-blue-200 rounded px-2 py-1"
            />
        </div>
        <!-- rest of template -->
    </template>
    <script>
    export default {
        // rest of component
        data() {
            return {
                // other data properties
                organization: ''
            }
        }
    }
    </script>
  4. 为了使突变的有效负载包含组织,我们需要将其添加到 $store.commit('profileUpdate') 有效负载中,并在组件触发 profileClear 突变(mutation)时在表单中重置它:

    <script>
    export default {
        // rest of component
        methods: {
            submitForm() {
                this.$store.commit('profileUpdate', {
                    // rest of payload
                    organization: this.organization
                })
            },
            resetProfileForm() {
                // other resets
                this.organization = ''
            }
        }
    }
    </script>
  5. 为了显示组织(organization),我们需要使用条件 span 在 src/components/AppProfileDisplay.vue 中渲染它(在没有设置 organization 时隐藏 at):

<template>
    <!-- rest of template -->
    <p class="mt-2">
        {{ formData.occupation }}
        <span v-if="formData.organization">
            at {{ formData.organization }}
        </span>
    </p>
    <!-- rest of template -->
</template>

该应用程序现在允许我们捕获组织(organization)字段并显示它。

image 2023 10 16 17 55 28 987
Figure 7. Figure 8.31: Profile Card Generator with Organization field support, filled out and submitted

它也将使我们能够毫无问题地清除配置文件:

image 2023 10 16 17 56 34 661
Figure 8. Figure 8.32: Profile Card Generator with Organization field support, after Reset button click

我们现在已经了解了如何向使用 Vuex 的应用程序添加字段。相对于事件总线或在祖先组件中存储状态,Vuex 的最大好处之一是它可以随着您添加更多数据和操作而扩展。接下来的活动将展示这种力量。

活动 8.01:将电子邮件和电话号码添加到个人资料卡生成器

在个人资料生成器中,您可以查看个人资料以查找有关个人的一些信息。电子邮件和电话号码通常是在个人资料卡上查找的最重要的信息。此活动是将这些详细信息添加到个人资料卡生成器中。

为此,我们将在 AppProfileForm 和 AppProfileDisplay 中添加电子邮件和电话号码字段:

  1. 我们可以首先向 AppProfileForm 的 email 字段添加一个新的电子邮件输入字段和标签。

  2. 然后,我们可以向 AppProfileForm 添加一个新的 phone 输入字段(类型为 tel)和一个标签作为 Phone Number 字段:

    新字段如下所示:

    image 2023 10 16 18 03 44 779
    Figure 9. Figure 8.33: Application with new Email and Phone Number fields
  3. 然后,我们可以将 email 和 phone 字段添加到 src/store.js 中的初始状态和突变(mutations),以便组织在 profileUpdate 期间初始化、设置,并在 profileClear 期间重置。

  4. 我们需要使用 v-model 跟踪 src/components/AppProfileForm.vue 组件的本地状态中的 email,并在 data() 函数中对其进行初始化。

  5. 我们需要使用 v-model 跟踪 src/components/AppProfileForm.vue 组件的本地状态中的 phone,并在 data() 函数中对其进行初始化。

  6. 为了使突变(mutation)的有效负载包含电子邮件(email)和电话(phone),我们需要将其添加到 $store.commit('profileUpdate') 有效负载中。当组件触发 profileClear 突变(mutation)时,我们还希望在表单中重置它。

  7. 为了显示电子邮件(email),我们需要使用条件段落在 src/components/ AppProfileDisplay.vue 中渲染它(在没有设置电子邮件时隐藏 Email 标签)。

  8. 为了显示电话(phone),我们需要使用条件范围在 src/components/ AppProfileDisplay.vue 中渲染它(在没有电话设置时隐藏 Phone Number 标签)。

填写并提交表格后,申请表应如下所示:

image 2023 10 16 19 16 25 330
Figure 10. Figure 8.34: Application with Email and Phone Number fields