对于每个大项目来说,使用状态树 (store) 管理状态 (state) 十分有必要。这就是为什么 Nuxt.js 内核实现了 Vuex

使用状态树

Nuxt.js 会尝试找到应用根目录下的 store 目录,如果该目录存在,它将做以下的事情:

  1. 引用 vuex 模块
  2. vuex 模块 加到 vendors 构建配置中去
  3. 设置 Vue 根实例的 store 配置项

Nuxt.js 支持两种使用 store 的方式,你可以择一使用:

  • 普通方式: store/index.js 返回一个 Vuex.Store 实例
  • 模块方式: store 目录下的每个 .js 文件会被转换成为状态树指定命名的子模块 (当然,index 是根模块)

普通方式

使用普通方式的状态树,需要添加 store/index.js 文件,并对外暴露一个 Vuex.Store 实例:

  1. import Vue from 'vue'
  2. import Vuex from 'vuex'
  3. Vue.use(Vuex)
  4. const store = () => new Vuex.Store({
  5. state: {
  6. counter: 0
  7. },
  8. mutations: {
  9. increment (state) {
  10. state.counter++
  11. }
  12. }
  13. })
  14. export default store

Nuxt.js 内置引用了 vuex 模块,所以不需要额外安装。

现在我们可以在组件里面通过 this.$store 来使用状态树:

  1. <template>
  2. <button @click="$store.commit('increment')">{{ $store.state.counter }}</button>
  3. </template>

模块方式

状态树还可以拆分成为模块,store 目录下的每个 .js 文件会被转换成为状态树指定命名的子模块

使用状态树模块化的方式,store/index.js 不需要返回 Vuex.Store 实例,而应该直接将 statemutationsactions 暴露出来:

  1. export const state = () => ({
  2. counter: 0
  3. })
  4. export const mutations = {
  5. increment (state) {
  6. state.counter++
  7. }
  8. }

其他的模块文件也需要采用类似的方式,如 store/todos.js 文件:

  1. export const state = () => ({
  2. list: []
  3. })
  4. export const mutations = {
  5. add (state, text) {
  6. state.list.push({
  7. text: text,
  8. done: false
  9. })
  10. },
  11. remove (state, { todo }) {
  12. state.list.splice(state.list.indexOf(todo), 1)
  13. },
  14. toggle (state, todo) {
  15. todo.done = !todo.done
  16. }
  17. }

最终的状态树大概如下:

  1. new Vuex.Store({
  2. state: { counter: 0 },
  3. mutations: {
  4. increment (state) {
  5. state.counter++
  6. }
  7. },
  8. modules: {
  9. todos: {
  10. state: {
  11. list: []
  12. },
  13. mutations: {
  14. add (state, { text }) {
  15. state.list.push({
  16. text,
  17. done: false
  18. })
  19. },
  20. remove (state, { todo }) {
  21. state.list.splice(state.list.indexOf(todo), 1)
  22. },
  23. toggle (state, { todo }) {
  24. todo.done = !todo.done
  25. }
  26. }
  27. }
  28. }
  29. })

在页面组件 pages/todos.vue, 可以像下面这样使用 todos 模块:

  1. <template>
  2. <ul>
  3. <li v-for="todo in todos">
  4. <input type="checkbox" :checked="todo.done" @change="toggle(todo)">
  5. <span :class="{ done: todo.done }">{{ todo.text }}</span>
  6. </li>
  7. <li><input placeholder="What needs to be done?" @keyup.enter="addTodo"></li>
  8. </ul>
  9. </template>
  10. <script>
  11. import { mapMutations } from 'vuex'
  12. export default {
  13. computed: {
  14. todos () { return this.$store.state.todos.list }
  15. },
  16. methods: {
  17. addTodo (e) {
  18. this.$store.commit('todos/add', e.target.value)
  19. e.target.value = ''
  20. },
  21. ...mapMutations({
  22. toggle: 'todos/toggle'
  23. })
  24. }
  25. }
  26. </script>
  27. <style>
  28. .done {
  29. text-decoration: line-through;
  30. }
  31. </style>
你也可以在模块文件里返回 Vuex.Store 实例,但是这种情况下你需要手工设置应用的状态树。

fetch 方法

fetch 方法会在渲染页面前被调用,作用是填充状态树 (store) 数据,与 asyncData 方法类似,不同的是它不会设置组件的数据。

关于 fetch 方法的更多信息,请参考 页面 fetch 方法API

nuxtServerInit 方法

如果在状态树中指定了 nuxtServerInit 方法,Nuxt.js 调用它的时候会将页面的上下文对象作为第2个参数传给它(服务端调用时才会酱紫哟)。当我们想将服务端的一些数据传到客户端时,这个方法是灰常好用的。

举个例子,假设我们服务端的会话状态树里可以通过 req.session.user 来访问当前登录的用户。将该登录用户信息传给客户端的状态树,我们只需更新 store/index.js 如下:

  1. actions: {
  2. nuxtServerInit ({ commit }, { req }) {
  3. if (req.session.user) {
  4. commit('user', req.session.user)
  5. }
  6. }
  7. }

如果你使用状态树模块化的模式,只有主模块(即 store/index.js)适用设置该方法(其他模块设置了也不会被调用)。

nuxtServerInit 方法接收的上下文对象和 fetch 的一样,但不包括 context.redirect()context.error()