在 Nuxt 中使用 Vuex store
在 Nuxt 中,Vuex 已经为你安装好了。你只需要确保项目根目录下存在 /store/ 目录。如果你使用 create-nuxt-app 安装你的 Nuxt 项目,这个目录会在项目安装过程中自动生成。在 Nuxt 中,你可以用两种不同的模式创建你的 store:
-
Module
-
Classic(已弃用)。
由于 Classic 模式已被弃用,本书将只关注 Module 模式。所以,让我们在接下来的章节开始吧。
|
你可以在我们的 |
使用模块模式
与 Vue 应用不同,在 Nuxt 中,每个模块(包括根模块)的 namespaced 键默认设置为 true。此外,在 Nuxt 中,你不需要在 store 根目录中组装模块;你只需要在根文件和模块文件中将 state 导出为一个函数,将 mutations、getters 和 actions 导出为对象即可。让我们通过以下步骤开始:
-
创建
store根目录,如下所示:// store/index.js export const state = () => ({ number: 3 }) export const mutations = { mutation1 (state) { ... } } export const getters = { getter1 (state, getter) { ... } } export const actions = { action1 ({ state, commit }) { ... } }在
Nuxt中,Vuex的严格模式在开发环境下默认设置为true,并在生产模式下自动关闭,但你可以在开发环境下禁用它,如下所示:// store/index.js export const strict = false -
创建一个模块,如下所示:
// store/module1.js export const state = () => ({ number: 1 }) export const mutations = { mutation1 (state) { ... } } export const getters = { getter1 (state, getter, rootState) { ... } } export const actions = { action1 ({ state, commit, rootState }) { ... } }然后,就像我们在上一节中在
Vue应用中手动做的那样,store将会自动生成,如下所示:new Vuex.Store({ state: () => ({ number: 3 }), mutations: { mutation1 (state) { ... } }, getters: { getter1 (state, getter) { ... } }, actions: { action1 ({ state, commit }) { ... } }, modules: { module1: { namespaced: true, state: () => ({ number: 1 }), mutations: { mutation1 (state) { ... } } ... } } }) -
在任何页面的
<script>块中映射所有的store state、getter、mutation和action,如下所示:// pages/index.vue import { mapState, mapGetters, mapActions } from 'vuex' export default { computed: { ...mapState({ numberRoot: state => state.number, }), ...mapState('module1', { numberModule1: state => state.number, }), ...mapGetters({ getNumberRoot: 'getter1' }), ...mapGetters('module1', { getNumberModule1: 'getter1' }) }, methods: { ...mapActions({ doNumberRoot:'action1' }), ...mapActions('module1', { doNumberModule1:'action1' }) } } -
在
<template>块中显示计算属性和提交mutation的方法,如下所示:// pages/index.vue <p>{{ numberRoot }}, {{ getNumberRoot }}</p> <button v-on:click="doNumberRoot">x 2 (root)</button> <p>{{ numberModule1 }}, {{ getNumberModule1 }}</p> <button v-on:click="doNumberModule1">x 2 (module1)</button>你应该在屏幕上看到以下初始结果,当你点击模板中显示的先前按钮时,它们将被改变:
3, 3 1, 1
正如我们之前提到的,在 Nuxt 中你不需要在 store 根目录中组装模块,因为如果你使用以下结构,Nuxt 会为你 “组装” 它们:
// chapter-10/nuxt-universal/module-mode/
└── store
├── index.js
├── module1.js
├── module2.js
└── ...
但是,如果你像我们在 Vue 应用中那样,使用以下结构在 store 根目录中手动组装模块:
// chapter-10/vuex-sfc/structuring-modules/basic/
└── store
├── index.js
├── ...
└── modules
├── module1.js
└── module2.js
你将在 Nuxt 应用中收到以下错误:
ERROR [vuex] module namespace not found in mapState(): module1/
ERROR [vuex] module namespace not found in mapGetters(): module1/
要修复这些错误,你需要显式地告诉 Nuxt 这些模块的存放位置:
export default {
computed: {
..mapState('modules/module1', {
numberModule1: state => state.number,
}),
...mapGetters('modules/module1', {
getNumberModule1: 'getter1'
})
},
methods: {
...mapActions('modules/module1', {
doNumberModule1:'action1'
})
}
}
就像 Vue 应用中的 Vuex 一样,我们也可以在 Nuxt 应用中将 state、actions、mutations 和 getters 分割到不同的文件中。让我们在下一节看看如何做到这一点以及 Nuxt 中的不同之处。
使用模块文件
我们可以将模块中的大文件拆分成单独的文件——state.js、actions.js、mutations.js 和 getters.js——用于 store 的根目录和每个模块。让我们通过以下步骤来实现:
-
为
store根目录创建单独的state、actions、mutations和getters文件,如下所示:// store/state.js export default () => ({ number: 3 }) // store/mutations.js export default { mutation1 (state) { ... } } -
为模块创建单独的
state、actions、mutations和getters文件,如下所示:// store/module1/state.js export default () => ({ number: 1 }) // store/module1/mutations.js export default { mutation1 (state) { ... } }同样,在
Nuxt中,我们不需要像在Vue应用中那样使用index.js来组装这些单独的文件。只要我们使用以下结构,Nuxt就会为我们完成这项工作:// chapter-10/nuxt-universal/module-files/ └── store ├── state.js ├── action.js └── ... ├── module1 │ ├── state.js │ ├── mutations.js │ └── ... └── module2 ├── state.js ├── mutations.js └── ...我们可以将其与我们在
Vue应用中使用的以下结构进行比较,在该结构中,我们需要为store根目录和每个模块创建一个index.js文件,以从单独的文件中组装state、actions、mutations和getters:// chapter-10/vuex-sfc/structuring-modules/advanced/ └── store ├── index.js ├── action.js └── ... ├── module1 │ ├── index.js │ ├── state.js │ ├── mutations.js │ └── ... └── module2 ├── index.js ├── state.js ├── mutations.js └── ...
因此,Nuxt 中 store 是开箱即用的,并且为你省去了组装文件和注册模块的一些代码。很棒,不是吗?现在,让我们在下一节进一步探索如何在 Nuxt 中使用 fetch 方法动态地填充 store 的 state。
使用 fetch 方法
我们可以使用 fetch 方法在页面渲染之前填充 store 的 state。它的工作方式与我们已经介绍过的 asyncData 方法相同——它在每次组件加载之前被调用。它在服务器端只调用一次,然后在客户端导航到其他路由时调用。就像 asyncData 一样,我们可以将 async/await 与 fetch 方法一起用于异步数据。它在组件创建之后被调用,因此我们可以在 fetch 方法中通过 this 访问组件实例。因此,我们可以通过 this.$nuxt.context.store 访问 store。让我们通过以下步骤创建一个使用此方法的简单 Nuxt 应用:
-
在任何页面中使用
fetch方法异步地从远程API请求用户列表,如下所示:// pages/index.vue import axios from 'axios' export default { async fetch () { const { store } = this.$nuxt.context await store.dispatch('users/getUsers') } } -
创建一个包含
state、mutations和actions的用户模块,如下所示:// store/users/state.js export default () => ({ list: {} }) // store/users/mutations.js export default { setUsers (state, data) { state.list = data }, removeUser (state, id) { let found = state.list.find(todo => todo.id === id) state.list.splice(state.list.indexOf(found), 1) } } // store/users/actions.js export default { setUsers ({ commit }, data) { commit('setUsers', data) }, removeUser ({ commit }, id) { commit('removeUser', id) } }mutations和actions中的setUsers方法用于将用户列表设置到state中,而removeUser方法用于一次从state中删除一个用户。 -
将
state和actions中的方法映射到页面,如下所示:// pages/index.vue import { mapState, mapActions } from 'vuex' export default { computed: { ...mapState ('users', { users (state) { return state.list } }) }, methods: { ...mapActions('users', { removeUser: 'removeUser' }) } } -
在
<template>块中循环并显示用户列表,如下所示:// pages/index.vue <li v-for="(user, index) in users" v-bind:key="user.id"> {{ user.name }} <button class="button" von:click="removeUser(user.id)">Remove</button> </li>当你在浏览器中加载应用时,你应该在屏幕上看到用户列表,并且你可以点击 “Remove” 按钮来删除一个用户。我们也可以在
actions中使用async/await来获取远程数据,如下所示:// store/users/actions.js import axios from 'axios' export const actions = { async getUsers ({ commit }) { const { data } = await axios.get('https://jsonplaceholder.typicode.com/users') commit('setUsers', data) } }然后,我们可以像这样 dispatch
getUsersaction:// pages/index.vue export default { async fetch () { const { store } = this.$nuxt.context await store.dispatch('users/getUsers') } }
除了在 Nuxt 中使用 fetch 方法获取和填充 state 之外,我们还可以使用 nuxtServerInit action,它只在 Nuxt 中可用。让我们在下一节继续了解它。
使用 nuxtServerInit action
与仅在页面级组件中可用的 asyncData 方法和在所有 Vue 组件(包括页面级组件)中可用的 fetch 方法不同,nuxtServerInit action 是 Nuxt store 中保留的 store action 方法,只有在定义时才可用。它只能在 store 根目录的 index.js 文件中定义,并且仅在 Nuxt 应用启动之前在服务器端调用。与在服务器端调用然后在后续路由的客户端调用的 asyncData 和 fetch 方法不同,nuxtServerInit action 方法仅在服务器端调用一次,除非你在浏览器中刷新应用的任何页面。此外,与将 Nuxt 上下文对象作为其第一个参数的 asyncData 方法不同,nuxtServerInit action 方法将其作为其第二个参数。它接收的第一个参数是 store 上下文对象。让我们将这些上下文对象放入下表中:
| 方法 | 第一个参数 | 第二个参数(Nuxt 上下文) | 仅服务器端 | 页面组件 | 所有组件 | 仅在 store/index.js | 调用次数 |
|---|---|---|---|---|---|---|---|
asyncData |
Nuxt 上下文对象 |
无 |
是/否 |
是 |
否 |
否 |
每次加载组件(服务器端一次,客户端导航时) |
fetch |
组件实例 (this) |
Nuxt 上下文对象 |
是/否 |
是 |
是 |
否 |
每次加载组件(服务器端一次,客户端导航时) |
nuxtServerInit |
Store 上下文对象 |
Nuxt 上下文对象 |
是 |
否 |
否 |
是 |
仅在服务器端启动时一次(除非刷新页面) |
因此,当我们想从应用的任何页面从服务器端获取数据,然后用服务器数据填充 store 的 state 时,nuxtServerInit action 方法非常有用——例如,当用户登录我们的应用时,我们存储在服务器端会话中的已认证用户数据。此会话数据可以作为 Express 中的 req.session.authUser 或 Koa 中的 ctx.session.authUser 存储。然后,我们可以通过 req 对象将 ctx.session 传递给 nuxtServerInit。
让我们使用这个方法 action 创建一个简单的用户登录应用,并使用你在第八章 “添加服务器端框架” 中了解到的 Koa 作为服务器端 API。在我们可以将任何数据注入会话并使用 nuxtServerIni action 方法创建一个 store 之前,我们只需要对服务器端进行一些修改,我们可以通过以下步骤完成:
-
使用
npm安装会话包koa-session:$ npm install koa-session
-
导入并注册会话包作为中间件,如下所示:
// server/middlewares.js import session from 'koa-session' app.keys = ['some secret hurr'] app.use(session(app))
-
在服务器端创建两个路由,如下所示:
// server/routes.js router.post('/login', async (ctx, next) => { let request = ctx.request.body || {} if (request.username === 'demo' && request.password === 'demo') { ctx.session.authUser = { username: 'demo' } ctx.body = { username: 'demo' } } else { ctx.throw(401) } }) router.post('/logout', async (ctx, next) => { delete ctx.session.authUser ctx.body = { ok: true } })在上面的代码中,我们使用
/login路由将认证的用户数据authUser通过会话注入到Koa上下文ctx中,而/logout用于取消设置认证数据。 -
创建包含
authUser键的store state以保存认证数据:// store/state.js export default () => ({ authUser: null }) -
创建一个
mutation方法,将数据设置到前面state中的authUser键:// store/mutations.js export default { setUser (state, data) { state.authUser = data } } -
在
store根目录中创建一个index.js文件,包含以下actions:// store/index.js export const actions = { nuxtServerInit({ commit }, { req }) { if (req.ctx.session && req.ctx.session.authUser) { commit('setUser', req.ctx.session.authUser) } }, async login({ commit }, { username, password }) { const { data } = await axios.post('/api/login', { username, password }) commit('setUser', data.data) }, async logout({ commit }) { await axios.post('/api/logout') commit('setUser', null) } }在上面的代码中,
nuxtServerInitaction 方法用于从服务器访问会话数据,并通过提交setUsermutation 方法来填充store的state。login和logoutaction 方法用于验证用户登录凭据和取消设置它们。请注意,由于本书使用Koa作为服务器API,因此会话数据存储在req.ctx中。如果你使用的是Express,请使用以下代码:actions: { nuxtServerInit ({ commit }, { req }) { if (req.session.user) { commit('user', req.session.user) } } }就像
asyncData和fetch方法一样,nuxtServerInitaction 方法也可以是异步的。你只需要返回一个Promise,或者使用async/await语句,以便Nuxt服务器等待action异步完成,如下所示:actions: { async nuxtServerInit({ commit }) { await commit('setUser', req.ctx.session.authUser) } } -
创建一个表单以使用
store的action方法,如下所示:// pages/index.vue <form v-on:submit.prevent="login"> <input v-model="username" type="text" name="username" /> <input v-model="password" type="password" name="password" /> <button class="button" type="submit">Login</button> </form> <script> export default { data() { return { username: '', password: '' } }, methods: { async login() { await this.$store.dispatch('login', { username: this.username, password: this.password }) }, async logout() { await this.$store.dispatch('logout') } } } </script>我们简化了前面的代码和步骤 6 中的代码以适应此页面,但你可以在我们的
GitHub仓库/chapter-10/nuxt-universal/nuxtServerInit/中找到它们的完整版本。
做得好!你终于完成了 Nuxt 和 Vue 的一个令人兴奋的功能——Vuex store 的学习。这是一个漫长的章节,但它非常重要,因为在接下来的章节中我们将经常回到 Vuex 并使用它。现在,让我们总结一下你在本章中学到的内容。