之前了解过Vuex,并且做了个小demo,但是做完之后很久不用,忘了大半,到新公司又要用到的时候,就很捉急了。正好遇到清明节,于是来重新认真过一遍文档,写一篇笔记来好好搞懂他,方便在工作中使用。

参考资料:

安装&使用

npm install vuex --save

通过Vue.use()来使用:

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

Vue.use(Vuex)

Vuex是什么

Vuex是一个专为vue.js应用程序开发的状态管理模式。它集中储存该应用的所有数据,统一保管。便于维护。

核心概念


state

vuex使用单一状态树,也就是一个对象包含了整个应用的所有状态,它作为唯一的数据源。也就是说,每个应用仅有一个store实例。

在Vue组件中获得Vuex状态

Vuex用过store选项,提供了一种机制,将数据从跟组件注入到每个子组件中(需要调用Vue.use(Vuex)):

import store from './store'

new Vue({
  el: '#app',
  // 把 store 对象提供给 “store” 选项,这可以把 store 的实例注入所有的子组件
  store,
  template: '<App/>',
  components: { App }
})

通过在根实例中注册store选项,该store实例会注入到根组件下的所有子组件中。且子组件能通过this.$store访问到:

const Counter = {
  template: `<div>{{ count }}</div>`,
  //从 store 实例中读取状态最简单的方法就是在计算属性中返回某个状态:
  computed: {
    count () {
      return this.$store.state.count
    }
  }
}

mapState辅助函数

当一个组件需要获取多个状态时,逐个声明计算属性会很麻烦,为此我们可以使用mapState辅助函数帮我们生成:

// 在单独构建的版本中辅助函数为 Vuex.mapState
import { mapState } from 'vuex'

export default {
  // ...
  computed: mapState({
     /*三种不同的方式*/
    // 箭头函数可使代码更简练
    count: state => state.count,
    // 传字符串参数 'count' 等同于 `state => state.count`
    countAlias: 'count',
    // 为了能够使用 `this` 获取局部状态,必须使用常规函数
    countPlusLocalState (state) {
      return state.count + this.localCount
    }
  })
}

上边我们给mapState传了一个对象,如果我们要生成的计算属性名称与state子节点名称相同时,也可以直接给mapState传入一个字符串数组:

computed: mapState([
  // 映射 this.count 为 store.state.count
  'count'
])

对象展开运算符

mapState函数返回的是一个对象,但是一个组件中的计算属性,不仅有来自store的,还有它局部的。那么如何混用呢?我们使用对象展开运算符:

computed: {
  //localComputed 是组件的局部计算属性
  localComputed () { /* ... */ },
  // 使用对象展开运算符将此对象混入到外部对象中
  ...mapState({
    // ...
  })
}

组件仍保有局部状态

上边说了组件的局部状态,也就是只有这个组件自己需要用到的数据,比如模态框组件是否显示的状态,这个数据只对该组件本身有意义,所以不应该放入Vuex,作为局部状态,反而更利于维护。

Getters

有时候我们需要从store中的state中派生出一些状态,例如对列表进行过滤并计数:

computed: {
  doneTodosCount () {
    return this.$store.state.todos.filter(todo => todo.done).length
  }
}

但如果我们要在多个组件中使用此属性,难道要复制这个函数吗?Vuex允许我们在store中定义getters(可以认为是store的计算属性)。getter接受state作为第一个参数:

//'store/index.js'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)
    },
    // getters也可以接受其他getters作为第二个参数
    doneTodosCount: (state, getters) => {
      return getters.doneTodos.length
    }
  }
})

getters会暴露为store.getters对象:

store.getters.doneTodos // -> [{ id: 1, text: '...', done: true }]

mapGetters辅助函数

mapGetters辅助函数仅仅是将store中的getters映射到局部计算属性:

import { mapGetters } from 'vuex'

export default {
  // ...
  computed: {
  // 使用对象展开运算符将 getters 混入 computed 对象中
    ...mapGetters([
      'doneTodosCount',
      'anotherGetter',
      // ...
    ])
    //如果想给getter属性领取一个名字,可以对象形式:
    mapGetters({
      // 映射 this.doneCount 为 store.getters.doneTodosCount
      doneCount: 'doneTodosCount'
    })
  }
}

Mutations

更改Vuex的store中的状态的唯一方法是提交mutation。Vuex的mutataions非常类似于事件:每个mutation都有一个字符串的事件类型(type)和一个回调函数(handler)。这个回调函数就是我们实际进行状态更改的地方,他会接受state作为第一个参数:

const store = new Vuex.Store({
  state: {
    count: 1
  },
  mutations: {
    increment (state) {
      // 变更状态
      state.count++
    }
  }
})

上边注册了一个类型为increment的mutation:“当触发一个类型为increment的mutation时,调用此函数。”实际使用时:store.commit('increment')

提交载荷(Payload)

载荷其实就是要传入vuex的数据对象啦。

你可以向store.commit传入额外的参数,即mutation的载荷(payload):

//注册mutation
mutations: {
  // 这里的传入的数据对象payload就是载荷
  increment (state, payload) {
    state.count += payload.amount
  }
}

//调用mutation的handler
store.commit('increment', {
  amount: 10
})
//还可以使用对象风格来调用
store.commit({
  type: 'increment',
  amount: 10
})

Mutations需遵守Vuex的响应规则
既然Vuex的store中的状态时响应式的,那么当我们变更状态时,监视状态的vue组件也会自动更新。所以同样要遵守vue的响应式注意事项:

  1. 最好提前在store中初始化好所有属性
  2. 当需要在对象上添加新属性时:1.用Vue.set;2.用新对象替换老对象。

使用常量替代Mutation事件类型

使用常量替代 mutation 事件类型在各种 Flux 实现中是很常见的模式。这样可以使 linter 之类的工具发挥作用,同时把这些常量放在单独的文件中可以让你的代码合作者对整个 app 包含的 mutation 一目了然:

// mutation-types.js
export const SOME_MUTATION = 'SOME_MUTATION'


// store.js
import Vuex from 'vuex'
import * as types from './mutation-types'

const store = new Vuex.Store({
  state: { ... },
  mutations: {
    // 我们可以使用 ES2015 风格的计算属性命名功能来使用一个常量作为函数名
    [types.SOME_MUTATION] (state) {
      // mutate state
    }
  }
})

mutation必须是同步函数

一条重要的原则就是要记住 mutation 必须是同步函数

在组件中提交Mutations

你可以在组件中使用this.store.commit('type')提交mutataion,或者使用mapMutations辅助函数将组件中的methods映射为store.commit调用(需要在根节点注入store)。

import { mapMutations } from 'vuex'

export default {
  // ...
  methods: {
    ...mapMutations([
      'increment' // 映射 this.increment() 为 this.$store.commit('increment')
    ]),
    ...mapMutations({
      add: 'increment' // 映射 this.add() 为 this.$store.commit('increment')
    })
  }
}

Actions

Action类似于mutation,不同之处在于:

  • Action提交的是mutataion,而不是直接变更状态。
  • Action可以包含任意异步操作。

注册一个简单的Action:

const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment (state) {
      state.count++
    }
  },
  actions: {
    increment (context) {
      context.commit('increment')
    }
  }
})

Action 函数接受一个与 store 实例具有相同方法和属性的 context 对象,因此你可以调用 context.commit 提交一个 mutation,或者通过 context.state 和 context.getters 来获取 state 和 getters。当我们在之后介绍到 Modules 时,你就知道 context 对象为什么不是 store 实例本身了。

解构:ES6允许按照一定模式,从数组和对象中提取值,对变量进行赋值。

//例如:赋值  var [a,b,c] = [1,2,3]
//例如:交换变量 [x,y] = [y,x]
//例如:函数参数解构:
// function add([x, y]){
//   return x + y;
// }
// add([1, 2]); // 3


//实践中我们常用到 参数解构 来简化代码,下边
//的 { commit } 就用到了解构。

actions: {
  increment ({ commit }) {
    commit('increment')
  }
}

这里解构的对象是context对象,也就是说context <==> { commit },这样写就可以用commit替代context.commit,简化代码。

分发Action

Action通过store.dispatch方法触发:store.dispatch('increment')
Actions 支持同样的载荷方式和对象方式进行分发:

// 以载荷形式分发
store.dispatch('incrementAsync', {
  amount: 10
})

// 以对象形式分发
store.dispatch({
  type: 'incrementAsync',
  amount: 10
})

我们可以在Action内部执行异步操作:

actions: {
  incrementAsync ({ commit }) {
    setTimeout(() => {
      commit('increment')
    }, 1000)
  }
}

在组件中分发Action

你在组件中使用 this.$store.dispatch('xxx') 分发 action,或者使用 mapActions 辅助函数将组件的 methods 映射为 store.dispatch 调用(需要先在根节点注入 store):

import { mapActions } from 'vuex'

export default {
  // ...
  methods: {
    ...mapActions([
      'increment' // 映射 this.increment() 为 this.$store.dispatch('increment')
    ]),
    ...mapActions({
      add: 'increment' // 映射 this.add() 为 this.$store.dispatch('increment')
    })
  }
}

组合Action

Modules

使用单一状态树,导致应用的所有状态集中到一个很大的对象。但是,当应用变得很大时,store 对象会变得臃肿不堪。

为了解决以上问题,Vuex 允许我们将 store 分割到模块(module)。每个模块拥有自己的 state、mutation、action、getters、甚至是嵌套子模块——从上至下进行类似的分割:

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 的状态
Logo

前往低代码交流专区

更多推荐