Vue学习笔记(四)

Vuex 是什么?

官网描述为:

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。

简单来说 Vuex 是一个用来集中式管理状态,或者说数据的一个插件,同时可以用来实现任意组件间的通讯

我们什么时候需要用到这个 Vuex 呢?上一篇的末尾我们提到过,当你需要多个组件共同管理某些数据,或者说是共享某些数据时,就需要用到 Vuex




Vuex 的安装和使用

安装就不多说了,和安装普通 npm 包一样

npm i vuex

使用方式和普通的插件差不多,不过按照官方的规范,我们应该在 src 目录下建立一个新的文件夹 store ,并在其中创建 index.js 文件,并在其中进行配置和导入

// src/store/index.js
//导入 Vue 核心库
import Vue from 'vue'
//导入 Vuex
import Vuex from 'vuex'
//使用 Vuex插件
Vue.use(Vuex)

//几个相关配置项
const actions = {}

const mutations = {}

const state = {}

//创建并导出 store
export default new Vuex.Store({
	actions,
	mutations,
	state
})

最后在 main.js 中导入 store ,并传入 store 配置项

//main.js
...

//导入store
import store from './store'

...

//创建 Vue实例
new Vue({
	el:'#app',
	render: h => h(App),
	store //传入store配置项
})

然后我们就可以开始使用 Vuex 了,当然在这之前我们需要先把 Vuex 的配置项配好




Vuex 的核心概念

这是一张 Vuex 的原理图,虚线框内的三个球就是 Vuex 的核心,除此之外还有个东西叫 Store ,前面三个核心就是由 Store 来负责管理的,这也就是为什么 Vuex 的文件夹不叫 Vuex 而叫 Store,传入的配置项也叫 Store

State

状态管理器管理的是什么,自然是状态,这个 State里面存放的就是要管理的状态,或者说是数据,你可以类别为是 Vue 里的 data,概念上基本类似,区别只是一个是由 Vue 实例来管理的,一个则是 Store 来管理的。

state: {
    count: 1
  }

Mutation

更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。如果我们想要修改 state 里的数据,就必须通过 Mutation,因此里面存放的就是各种修改 State 的方法。(这里我们说是唯一修改状态的方法,不是说只有 mutation 能改 State,其他的方法要改也是可以改的,但是用其他的来修改 State 是错误的使用方法)

mutations: {
    increment (state, n) {
      // 变更状态
      state.count += n
    }
  }

还有一个重要的原则是 Mutation 必须是同步函数,你不应该在其中使用异步的函数,最好的方式是 Mutation 中只修改 State 里的数据,不做任何其他多余的操作

最后如何提交 mutation 呢?前面说了所有的这些东西都是由 Store 来进行管理的,因此自然通过 Store 来操作,方式有些类似事件,需要主动的触发,如果需要传参数,就往后面加即可

store.commit('increment', n)

Action

刚刚上面说了,Mutation 只负责修改 State 不做其他多余的操作,那么其他多余的操作给谁呢,自然就是 Action

所有修改 State 前的判断、发 ajax 请求等操作就放在 Action,如果一些很简单的操作,不需要这些额外的操作,也可以不需要 Action,绕过这一步,直接提交 Mutation,比如下面这个 action 就显得可有可无了一点

actions: {
    increment (context, n) {
      context.commit('increment', n)
    }
  }

与 mutation 不同的是,mutation 中因为只需要修改 State,所以第一个参数只给了 State,而 action 中可以做很多事情,比如根据 state 里的数据进行某些判断,比如 commit 给 mutation,比如调用 action 中的其他函数,因此它的第一个参数是一个与 store 实例具有相同方法和属性的 context 对象,至于为什么是 context 而不是 Store,是因为后面我们有可能将 Store 给拆成多个模块

触发 action 的方式和 mutation 差不多,不是提交而是分发

store.dispatch('increment', n)

Getter

除了上面三个之外,还有个 getter,属于额外的配置项,至于有什么用的,相当于是 Vuex 里的计算属性,computed 对于 data 是怎么样的,getter 对于 state 就是什么样的,因此就不多说了

const store = new Vuex.Store({
  state: {
    todos: [
      { id: 1, text: '...', done: true },
      { id: 2, text: '...', done: false }
    ]
  },
  getters: {
    doneTodos: state => {
      return state.todos.filter(todo => todo.done)
    }
  }
})




最后理一下整个流程,就是上面那张原理图,先是在组件中 dispatch 给 action,然后在 action 中进行如判断和异步处理之类的操作,然后 commit 给 mutation,最后在 mutation 中修改 state

在组件里只要通过 $store 就可以访问到 Store




四个map方法

  1. mapState:用于帮助我们映射 state中的数据为计算属性

    computed: {
        //对象写法
        ...mapState({count:'count'}),
        //数组写法
        ...mapState(['count']),
        //等价于
        count(){
            return this.$store.state.count
        },
    }
    
  2. mapGetters:用于帮助我们映射 getters中的数据为计算属性

    computed: {
        //对象写法
        ...mapGetters({anotherCount:'anotherCount'}),
        //数组写法
        ...mapGetters(['anotherCount'])
    
        //等价于
        anotherCountm(){
            return this.$store.getters.anotherCount
        }
    },
    
  3. mapActions:用于帮助我们生成与 actions对话的方法

    methods:{
        //对象形式
        ...mapActions({increment:'increment'})
    
        //数组形式
        ...mapActions(['increment'])
    
        //等价于
        increment(value){
            this.$store.dispatch('increment',value)
        }
    }
    
  4. mapMutations:用于帮助我们生成与 mutations对话的方法

    methods:{
        //对象形式
        ...mapMutations({increment:'increment',decrement:'decrement'}),
    
        //数组形式
        ...mapMutations(['increment','decrement']),
    
        //等价于
        increment(value){
            this.$store.commit('increment',value)
        }
    }
    




Module

模块化有什么好处就不用说了吧,Vuex 也支持模块化,把一个store 拆分成多个模块

用法也和组件的用法有点类似,不过比组件简单很多

const moduleA = {
  state: () => ({ ... }),
  mutations: { ... },
  actions: { ... },
  getters: { ... }
}

const moduleB = {
  state: () => ({ ... }),
  mutations: { ... },
  actions: { ... }
}

const store = new Vuex.Store({
  modules: {
    a: moduleA,
    b: moduleB
  }
})

store.state.a // -> moduleA 的状态
store.state.b // -> moduleB 的状态

像上面这种用法的话,其实只有 state 被模块化了,而 action 、mutation 和getter 其实还是全局的,如果打算进一步的模块的化的,就需要开启命名空间

命名空间的作用类似于在你创建时给你定义的 action 、mutation 和 getter 修改一下名字,在前面加上模块名来作为前缀,形式上类似于 url 的写法(state 依旧是通过 . 来访问)

const store = new Vuex.Store({
  modules: {
    account: {
      namespaced: true,

      // 模块内容(module assets)
      state: () => ({ ... }), // 模块内的状态已经是嵌套的了,使用 `namespaced` 属性不会对其产生影响
      getters: {
        isAdmin () { ... } // -> getters['account/isAdmin']
      },
      actions: {
        login () { ... } // -> dispatch('account/login')
      },
      mutations: {
        login () { ... } // -> commit('account/login')
      },

      // 嵌套模块
      modules: {
        // 继承父模块的命名空间
        myPage: {
          state: () => ({ ... }),
          getters: {
            profile () { ... } // -> getters['account/profile']
          }
        },

        // 进一步嵌套命名空间
        posts: {
          namespaced: true,

          state: () => ({ ... }),
          getters: {
            popular () { ... } // -> getters['account/posts/popular']
          }
        }
      }
    }
  }
})

不过由于名字携带了命名空间,在使用时会更加的麻烦,当使用四个 map 函数时,可以通过传第一个参数为命名空间来简化一些

computed: {
  ...mapState('some/nested/module', {
    a: state => state.a,
    b: state => state.b
  })
},
methods: {
  ...mapActions('some/nested/module', [
    'foo', // -> this.foo()
    'bar' // -> this.bar()
  ])
}

如果你在某个组件中只使用一个种命名空间的模块的话,还可以通过 createNamespacedHelpers 函数来简化

import { createNamespacedHelpers } from 'vuex'

const { mapState, mapActions } = createNamespacedHelpers('some/nested/module')

export default {
  computed: {
    // 在 `some/nested/module` 中查找
    ...mapState({
      a: state => state.a,
      b: state => state.b
    })
  },
  methods: {
    // 在 `some/nested/module` 中查找
    ...mapActions([
      'foo',
      'bar'
    ])
  }
}