'vuex'

Vuex

Vuex是一个转为Vue.js程序开发的状态管理模式.用来几种管理所有组件的状态,并以响应的规则保证状态以一种可预测的方式发生变化.

状态管理应用包含三个部分

  1. state, 驱动应用的数据源
  2. view, 以声明方式将state映射到视图
  3. actions, 响应在view上的用户输入导致的状态变化

全局单例模式管理,可以将组建构成一个巨大的’视图’,不管在树的哪个位置,任何组建都能获取状态或者触发行为!

在简单的页面下可以使用event bus($on,$off,$emit)

  1. 没一个Vuex应用的核心就是store,’store’基本上就是一个容器,包含着应用中的大部state。Vuex和单纯的全局对象有一下两点不同:
    • 状态储存响应式
    • 不能直接改变store中的state

store 最简单的store实现

1
2
3
4
5
6
const store = new Vuex.Store({
state:{count:0},
matations:{
increment(state){state.count++}
}
})

State 单一状态树

Vuex使用单一状态树 – 用一个对象包含了全部的应用层级状态.作为一个(唯一数据源存在)SSOT.

在Vue组件中获得Vuex状态

Vuex 的状态储存是响应式的,从store实例中读取状态最简单的是在计算属性(computed)中直接返回某个状态

1
2
3
4
5
6
7
8
9
10
11
12
const Counter = {
template:`<div>{{count}}</div>`,
computed(){
count(){
return store.state.count
}
}
}
//在创建Vue实例时注入组件中(Vue.use(Vuex))
const app = new Vue({sotre})

每当store.state.count变化的时候,都会重新求取计算属性,并且触发更新相关联的DOM.

注入后能够通过this.$store访问到

mapState 辅助函数

当组件需要获取多个状态的时候,将这些状态都声明为计算属性会有些重复和冗余,因此使用mapState()可以帮助我们生成计算属性

js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
}
})
}

当映射的计算属性的名称与state的子节点名称相同时,可以给mapState传递一个数组

js
1
computed:mapState(['count'])

对象展开运算符

js
1
2
3
4
5
6
computed:{
localComputed(){},
...mapstate({
// ...
})
}

Getter

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

js
1
2
3
4
5
computed: {
doneTodosCount () {
return this.$store.state.todos.filter(todo => todo.done).length
}
}

Vuex 允许在store中定义getters对象

js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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
}
}
})
//可以较为容易的在任何组件中使用它:
computed:{
donTodosCount(){
return this.$store.getters.doneTodosCount
}
}

mapGetters 辅助函数

js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import { mapGetters } from 'vuex'
export default {
// ...
computed: {
// 使用对象展开运算符将 getters 混入 computed 对象中
...mapGetters([
'doneTodosCount',
'anotherGetter',
// ...
])
}
}
//将一个 getter 属性另取一个名字,使用对象形式:
mapGetters({
// 映射 this.doneCount 为 store.getters.doneTodosCount
doneCount: 'doneTodosCount'
})

Mutations

更改Vuex的sotre中的位移方法是提交matation。每个mutation都有一个字符串的type和一个handler().这个回调函数就是我们实际进行状态修改的地方,并且它会接收state作为第一个参数

js
1
2
3
4
5
6
7
8
9
10
11
const store = new Vuex.Store({
state: {
count: 1
},
mutations: {
increment (state) {
// 变更状态
state.count++
}
}
})

需要在组件中调用相应的type调用store.commit方法:

1
2
3
4
5
6
7
8
9
10
mutations定义的方法可以传入第二个参数即荷载(payload)
```apple js
// ...
mutations: {
increment (state, n) {
state.count += n
}
}
store.commit('increment', 10)

大多数请概况下payload应该是一个Object,这样可以包含多个字段并且记录mutation会更易读

提交mutation的另一种风格

js
1
2
3
4
store.commit({
type: 'increment',
amount: 10 //payload
})

Mutations需要遵守的Vue的响应规则

  1. 最好在store中初始化好所有所需属性
  2. 当需要在对象上添加新属性时,你应该
    • 是用Vue.set(obj,'newProp',12)
    • 以新对象替换老对象:state.obj = {...state.obj,newProp:123}

mutations方法名称最好用常量替代
mutations 必须是同步函数
在组件中使用this.$store.commit('xxx')提交mutation,或者使用mapMutations()将组件中的methods映射为store.commit调用

js
1
2
3
4
5
6
7
8
9
10
11
12
13
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提交的是mutation,而不是直接变更状态
- Action可以包含任意异步操作
js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment (state) {
state.count++
}
},
actions: {
increment (context) {
context.commit('increment')
}
}
})

Action函数接收一个与store对象具有相同方法和属性的context对象。

可以用到结构赋值

js
1
2
3
4
5
actions:{
increment({commit}){
commit('increment')
}
}

通过store.dispatch()方法触发

js
1
store.dispatch('increment');
  1. 可以内嵌异步方法
  2. Actions同Mutation一样支持payload和对象方式进行发布
  3. 调用异步API和分发多从mutations

    购物车例子

    js
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    actions: {
    checkout ({ commit, state }, products) {
    // 把当前购物车的物品备份起来
    const savedCartItems = [...state.cart.added]
    // 发出结账请求,然后乐观地清空购物车
    commit(types.CHECKOUT_REQUEST)
    // 购物 API 接受一个成功回调和一个失败回调
    shop.buyProducts(
    products,
    // 成功操作
    () => commit(types.CHECKOUT_SUCCESS),
    // 失败操作
    () => commit(types.CHECKOUT_FAILURE, savedCartItems)
    )
    }
    }

组件中分发Action

在组件中使用this.$store.dispatch('xxx')分发action,或者使用mapActions()辅助函数将组件的methods映射为store.dispatch调用

js
1
2
3
4
5
6
7
8
9
10
11
12
13
import { mapActions } from 'vuex'
export default {
// ...
methods: {
...mapActions([
'increment' // 映射 this.increment() 为 this.$store.dispatch('increment')
]),
...mapActions({
add: 'increment' // 映射 this.add() 为 this.$store.dispatch('increment')
})
}
}

组合Actions

dispatch()可以处理被触发的action的回调函数返回的Promise,并且store.dispatch()返回的仍旧是Promise

js
1
2
3
4
5
6
7
8
9
10
actions:{
actionA({commit}){
return Promise((resolve,reject)=>{
setTimeout(() => {
commit('someMutation')
resolve()
}, 1000)
})
}
}

现在你可以:

1
2
3
store.dispatch('actionA').then(() => {
// ...
})

在另外一个 action 中也可以:

1
2
3
4
5
6
7
8
9
actions: {
// ...
actionB ({ dispatch, commit }) {
return dispatch('actionA').then(() => {
commit('someOtherMutation')
})
}
}

最后,如果我们利用 async / await 这个 JavaScript 即将到来的新特性,我们可以像这样组合 action:

假设 getData() 和 getOtherData() 返回的是 Promise

1
2
3
4
5
6
7
8
9
actions: {
async actionA ({ commit }) {
commit('gotData', await getData())
},
async actionB ({ dispatch, commit }) {
await dispatch('actionA') // 等待 actionA 完成
commit('gotOtherData', await getOtherData())
}
}

Modules (https://vuex.vuejs.org/zh-cn/modules.html)

项目结构

  1. 应用层级的状态应该集中到单个store对象中.
  2. 提交mutation是更改状态的位一方法,并且这个过程是同步的.
  3. 异步逻辑都应该封装到action里面.

对于大型应用,Vuex相关代码分割到模块中.下面是项目结构示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
├── index.html
├── main.js
├── api
│ └── ... # 抽取出API请求
├── components
│ ├── App.vue
│ └── ...
└── store
├── index.js # 我们组装模块并导出 store 的地方
├── actions.js # 根级别的 action
├── mutations.js # 根级别的 mutation
└── modules
├── cart.js # 购物车模块
└── products.js # 产品模块

插件