方法二 – 使用模块

在之前的方法中,我们主要只是将代码行移动到其它文件中。正如我们所说,虽然这使得 store 本身的工作变得更容易,但它并没有改变 Vue 组件使用 store 的方式。模块(Modules)帮助我们处理组件所在级别的复杂性。

想象一个大型状态(state)对象,其中包含代表许多不同事物的值,例如:

state: {
    name:"Lindy",
    favoriteColor: "blue",
    profession: "librarian",
    // lots more values about Lindy
    books: [
        { name: "An Umbrella on Fire", pages: 283 },
        { name: "Unicorn Whisperer", pages: 501 },
        // many, many more books
    ],
    robots: {
        skill:'advanced',
        totalAllowed: 10,
        robots: [
            { name: "Draconis" },
            // so much robots
        ]
    }
}

这个示例包含一个人的信息、与书籍相关的数据和一组代表机器人的值。这些数据涵盖了三个完全不同的主题。把这些数据移到自己的文件中并不一定更容易使用或有助于保持条理清晰。这种复杂性也会蔓延到获取器(getters)、突变(mutations)和操作(actions)中。给定一个名为 setName 的操作(action),你可以认为它适用于代表人的状态值,但如果其它状态值也有类似的名称,就会开始变得混乱。

这就是模块的用武之地。模块允许我们在存储中为部分数据定义一个单独的存储桶。每个模块都可以有独特的状态(state)、获取器(getters)、突变(mutations)和操作(actions),与根存储或核心存储完全分离。

下面是一个使用简历(resume)模块的存储示例:

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

Vue.use(Vuex)

export default new Vuex.Store({
state: {
    firstName:'Raymond',
    lastName:'Camden'
},
getters: {
    name(state) {
        return state.firstName + ' ' + state.lastName;
    }
},
modules: {
    resume: {
        state: {
            forHire:true,
            jobs: [
                "Librarian",
                "Jedi",
                "Cat Herder"
            ]
        },
        getters: {
            totalJobs(state) {
                return state.jobs.length;
            }
        }
    }
}
})

根(root)存储和模块(module)都定义状态(state)和获取器(getters),但也可以公开突变(mutations)和操作(actions)。请注意,在简历(resume)模块 getters、totalJobs 中,状态变量如何引用其自身的状态,而不是父级的状态。这很棒,因为它确保您可以在模块内工作,而不必担心意外修改根或其它模块中的另一个值。您可以使用新的第三个参数 rootState 来访问 getter 中的根状态:

totalJobs(state, anyArgument, rootState)

操作(action)可以通过上下文对象 context.rootState 使用 rootState。但从理论上讲,您的模块应该关注自己的数据,并且仅在必要时才访问根目录。

使用模块值时,您的代码必须知道模块的名称。考虑以下示例:

first name {{ $store.state.firstName }}<br/>
for hire? {{ $store.state.resume.forHire }}<br/>

不过,getters、action 和 mutations 并没有区别。您可以这样访问两个获取器:

full name {{ $store.getters.name }}<br/>
total jobs {{ $store.getters.totalJobs }}<br/>

其背后的想法是允许一个或多个模块潜在地响应相同的调用。如果这对您没有吸引力,您可以通过传入命名空间(namespaced)选项来命名您的模块:

modules: {
    resume: {
        namespaced: true,
        state: {
            forHire:true,
            jobs: [
                "Librarian",
                "Jedi",
                "Cat Herder"
            ]
        },
        getters: {
            totalJobs(state) {
                return state.jobs.length;
            }
        }
    }
}

然后,要引用此模块的 getters、mutations 和 actions,您必须在调用过程中传入模块的名称。因此,例如,getter 现在变为:$store.getters['resume/totalJobs']

在大多数情况下,这就是模块支持的核心内容,但请注意,模块在全局范围内暴露自己的方式还有更多选项,这不在本书的讨论范围之内。请参阅模块文档( https://vuex.vuejs.org/guide/modules.html )的后半部分,了解相关示例。最后要注意的是,你可以在模块中构建模块,深度不限。显然,在构建嵌套极深的模块时需要花些心思,但如果你想这样做,Vuex 是允许的!

练习 11.02:使用模块

在本练习中,我们将使用一个 Vuex 存储,该存储使用了两个模块,为了增加趣味性,其中一个模块将存储在另一个文件中,这表明我们在使用模块时也可以使用第一种方法。

要访问本练习的代码文件,请访问 https://packt.live/35d1zDv

  1. 像往常一样,生成一个新的 Vue 应用程序并确保添加 Vuex。

  2. store 文件 (store/index.js) 中,添加名字和姓氏的两个状态(state)值以及一个返回两者的 getter:

    state: {
        firstName:'Raymond',
        lastName:'Camden'
    },
    getters: {
        name(state) {
            return state.firstName + ' ' + state.lastName;
        }
    },
  3. 接下来,将 resume 模块添加到存储(store)文件中。它将有两个状态(state)值,一个代表开放招聘值(forHire),另一个代表一系列过去的工作(jobs)。最后,添加一个 getter 以返回 jobs 总数:

    modules: {
        resume: {
            state: {
                forHire:true,
                jobs: [
                    "Librarian",
                    "Jedi",
                    "Cat Herder"
                ]
            },
            getters: {
                totalJobs(state) {
                    return state.jobs.length;
                }
            }
        },
  4. 现在为下一个模块创建一个新文件 store/portfolio.js。这将包含一个表示正在运行的网站数组的状态值以及一个用于添加值的突变:

    export default {
        state: {
            websites: [
                "https://www.raymondcamden.com",
                "https://codabreaker.rocks"
            ]
        },
        mutations: {
            addSite(state, url) {
                state.websites.push(url);
            }
        }
    }
  5. 回到主 store 的 index.js 文件,导入 portfolio:

    import portfolio from './portfolio.js';
  6. 然后在简历(resume)之后的模块列表中添加投资组合(portfolio):

    modules: {
        resume: {
            state: {
                forHire:true,
                jobs: [
                    "Librarian",
                    "Jedi",
                    "Cat Herder"
                ]
            },
            getters: {
                totalJobs(state) {
                    return state.jobs.length;
                }
            }
        },
        portfolio
    }
  7. 现在,让我们使用主 src/App.vue 文件中的模块。修改模板以添加对 store 各个部分的调用:

    <p>
        My name is {{ $store.getters.name }} and I
        <span v-if="$store.state.resume.forHire">
        am looking for work!
        </span><span v-else>
        am not looking for work.
        </span>
    </p>
    <p>
        I've had {{ $store.getters.totalJobs }} total jobs.
    </p>
    <h2>Portfolio</h2>
    <ul>
        <li
        v-for="(site,idx) in $store.state.portfolio.websites"
        :key="idx"><a :href="site" target="_new">{{ site }}</a></li>
    </ul>
  8. 然后添加一个表单(form),以便我们可以添加一个新网站:

    <p>
        <input type="url" placeholder="New site for portfolio"  v-model="site">
        <button @click="addSite">Add Site</button>
    </p>
  9. 定义 addSite 的方法。它将提交突变(mutation)并清除站点值。请务必为站点添加本地数据值。这是完整的脚本块:

    export default {
        name: 'app',
        data() {
            return {
                site:''
            }
        },
        methods: {
            addSite() {
                this.$store.commit('addSite', this.site);
                this.site = '';
            }
        }
    }

结果如下:

image 2023 10 17 09 47 03 293
Figure 1. Figure 11.2: The application making use of Vuex data with modules

现在您已经看到了另一种帮助管理 Vuex store 的方法。模块提供了一种更深入、更复杂的方式来组织 store。与往常一样,选择最适合您的应用需求以及您和您的团队最熟悉的方法!