官网

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式

状态管理模式

状态管理模式可以简单的将其看成把需要多个组件共享的变量全部存储在一个对象里面,然后将这个对象放在顶层的 Vue 实例中,让其他组件可以使用

以一个计数应用为例:
image.png

这个状态自管理应用包含以下几个部分:

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

image.png

安装和使用

安装

npm install Vuex —save

配置

  1. // src/store/index.js
  2. import Vue from 'vue'
  3. import Vuex from 'vuex'
  4. // 1.安装插件
  5. Vue.use(Vuex)
  6. // 2.创建对象
  7. const store = new Vuex.Store({
  8. // 保存状态
  9. state: {
  10. counter: 1000
  11. },
  12. })
  13. // 3.导出 store 对象
  14. export default store
  15. // 4.将 store 挂载到 Vue 实例
  1. import store from './store';
  2. new Vue({
  3. el: '#app',
  4. store,
  5. render: h => h(App)
  6. })

使用

  1. // src/App.vue
  2. <template>
  3. <div id="app">
  4. // 或者 this.$store.state.counter
  5. <h2>{{ $store.state.counter }}</h2>
  6. </div>
  7. </template>

状态管理图例

Vuex(五) - 图3
不要通过 Vue Components 来直接修改 State,因为当多个视图依赖同一状态时代码将变得不可维护。通过 Mutations 来修改 State,Devtolls(一个浏览器插件) 将会记录 State 的状态(由谁改变的 State)

可以通过 Mutatiions 来直接修改 State,而不需要 Actions。Actions 的作用是用来处理异步操作的,当 Mutations 中有异步操作时,Devtools 将记录不到 State 的状态

devtools

使用

下载浏览器扩展

image.png

mutations

通过 mutations 修改 state 的值会被 devtools 监测到

配置

// src/store/index.js
const store = new Vuex.Store({
  // 保存状态
  state: {
    counter: 1000
  },
  // 方法
  mutations: {
    // state 将会作为参数自动传入 mutations
    increment(state) {
      state.counter++
    },
    decrement(state) {
      state.counter--
    },
    // 获取提交过来的参数,如果是多个参数使用对象
    incrementCount(state, count) {
      state.counter += count
    }
  },
})

使用

// src/App.vue
<template>
  <div id="app">
    <button @click="addition">+</button>
    <button @click="subtraction">-</button>
    <button @click="addCount(10)">+10</button>
  </div>
</template>

<script>
export default {
  name: "App",
  methods: {
    // 通过 commit 提交
    addition() {
      this.$store.commit("increment");
    },
    subtraction() {
      this.$store.commit("decrement");
    },
    // 携带参数提交,如果是多个参数使用对象
    addCount(count) {
      this.$store.commit("incrementCount", count);
    },
  },
};
</script>

通过 devtools 监听 state
image.png

Payload

参数被称为是 mutation 的载荷(Payload)

mutation 由两部分组成

  • 字符串的事件类型(type)
  • 一个回调函数(handler),该回调函数的第一个参数是 state

image.png

延续上面的例子使用 Payload 的提交方式

// src/store/index.js
const store = new Vuex.Store({
  // 保存状态
  state: {
    counter: 1000
  },
  // 方法
  mutations: {
    // 此时接收过来的参数是一个对象
    incrementCount(state, payload) {
      state.counter += payload.count
    }
  },
})
// src/App.vue
<template>
  <div id="app">
    <button @click="addCount(10)">+10</button>
  </div>
</template>

<script>
export default {
  name: "App",
  methods: {
    // 使用 Payload 方式提交
    addCount(count) {
      this.$store.commit({
          type: 'incrementCount',
        count
      });
    },
  },
};
</script>

响应式规则
**
Vuex 的 store 中的 state 是响应式的,当 state 中的数据发生改变时, V 组件会自动更新

但是要求我们必须遵守 Vuex 对应的规则

  • 提前在 store 中初始化好所需的属性
  • 当给 state 中的对象添加新属性时,使用下面的方式
    • 使用 Vue.set(需要修改的对象, 'key', 修改后的值)
    • 用新对象给旧对象重新赋值
  • 删除属性时使用 Vue.delete(需要修改的对象, '需要修改的key')
    const store = new Vuex.Store({
    // 保存状态
    state: {
      info: {
        name: 'kobe',
        age: 40,
        height: 1.98
      }
    },
    mutations: {
      updateInfo(state) {
        // 在 store 中定义了 info 所以是响应式的
        state.info.name = 'codewhy';
        // address 并没有在 store 中定义,所以不是响应式的
        state['address'] = '洛杉矶';
        // 使用了 Vue.set 方法,所以是响应式的
        Vue.set(state.info, 'weight', 45);
        // 不是响应式的
        delete state.info.age;
        // 是响应式的
        Vue.delete(state.info, 'age');
      }
    },
    })
    

类型常量

使用常量替代 mutation 事件类型

// src/store/mutation-types.js
export const INCREMENT = 'increment'
// src/store/index.js
import Vuex from 'vuex'
import { INCREMENT } from './mutation-types'

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

单一状态树

例如在我国有很多信息需要被记录,比如户口、医疗、文凭等等,这些信息被分散在很多地方进行管理。当你需要办理某个业务时(比如入户某个城市),就需要到各个对应的信息存放点去打印、盖章各种资料信息,最后到一个地方提交证明的信息无误。这种保存信息的方案不仅低效,而且不方便管理维护。

如果你的状态信息保存到多个 Store 对象中的,那么之后的管理和维护会变得特别困难。所以 Vuex 也使用了单一状态树来管理应用层级的全部状态。

getters

当 state 中的属性需要经过一系列变化时就可以使用 getters(类似于 computed)

配置

// src/store/index.js
const store = new Vuex.Store({
  // 保存状态
  state: {
    counter: 1000
  },
  getters: {
    // state 将会作为参数传入
    increment(state) {
        return state.counter + 1
    },
    // getters 将会作为第二个参数
    // 再通过 getters 拿到 increment
    powerCounter(state, getters) {
      return (state.counter * state.counter) + (getters.increment)
    },
    // 通过返回函数到接收参数
    decreament(state) {
      return num => {
        return state.counter - num
      }
    }
  },
})

使用

// src/App.vue
<template>
  <div id="app">
    // 或者 this.$store.getters.powerCounter
    <h2>{{ $store.getters.powerCounter }}</h2>  // 1001001
    <h2>{{ $store.getters.increment }}</h2>  // 1001
    <h2>{{ $store.getters.decreament(1) }}</h2>  // 999
  </div>
</template>

mapGetters

mapGetters 辅助函数仅仅是将 store 中的 getters 映射到局部计算属性,然后可以像使用 computed 一样使用 getters

import { mapGetters } from 'vuex'

export default {
  // ...
  computed: {
  // 使用对象展开运算符将 getter 混入 computed 对象中
    ...mapGetters([
      'doneTodosCount',  // getters 中的方法名
      'anotherGetter',
      // ...
    ])
  }
}

如果你想将一个 getter 属性另取一个名字,使用对象形式:

...mapGetters({
  // 把 `this.doneCount` 映射为 `this.$store.getters.doneTodosCount`
  doneCount: 'doneTodosCount'
})

actions

Vuex 要求我们 mutation 中的方法必须是同步方法,通过 actions 来处理异步方法

Action 类似于 mutation,不同在于:

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

配置

// src/store/index.js
const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment (state) {
      state.count++
    }
  },
  actions: {
    // content: 上下文
    // 此时 context 相当于  store
    // 同样也能像 mutation 一样接受参数
    increment (context, payload) {
      setTimeout(() => {
          context.commit('increment');
          console.log(payload);
      }, 1000)
    }
  }
})

使用

<template>
  <div id="app">
    <h2>{{ $store.state.count }}</h2>
    <button @click="addition">+</button>
  </div>
</template>

<script>
export default {
  components: { HelloVuex },
  name: "App",
  methods: {
    addition() {
      // 发送到 actions 通过 dispatch 提交
      this.$store.dispatch('increment', '我是payload')
    },
  },
};
</script>

示例

// src/store/index.js
const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment (state) {
      state.count++
    }
  },
  actions: {
    increment (context, payload) {
      // 谁调用了 dispatch 就将值返回给谁
      return new Promise((resolve, reject) => {
          setTimeout(() => {
              context.commit('increment');
              console.log(payload);
          resolve('111')
          }, 1000) 
      })
    }
  }
})
<template>
  <div id="app">
    <h2>{{ $store.state.count }}</h2>
    <button @click="addition">+</button>
  </div>
</template>

<script>
export default {
  components: { HelloVuex },
  name: "App",
  methods: {
    addition() {
      this.$store
        .dispatch('increment', '我是payload')
          .then(res => {
            console.log('里面完成了提交')
            console.log(res)
          })
    },
  },
};
</script>

modules

由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。
为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割:

const moduleA = {
  state: () => ({ 
      name: 'zhangsan'
  }),
  mutations: { 
      updateName(state, payload) {
      state.name = payload
    }
  },
  actions: { 
      upDateName(context) {
      setTimeout(() => {
        // 此时使用 context 的 commit 提交的是在自身的 mutations 中查找
        context.commit('updateName', 'wanwu')
      }, 1000);
    }
  },
  getters: { 
    // 和普通的 getters 一样可以传入 state 和 getters
      fullName(state) {
        return state.name + '111' 
    },
    fullName2(state, getters) {
      return getters.fullName + '2222'
    },
    // rootState 为最外层的 state
    fullName3(state, getters, rootState) {
      return getters.fullName2 + rootState.counter
    }
  }
}

const store = new Vuex.Store({
  state: {

    }
  modules: {
    a: moduleA,
  }
})

// 在 modules 中定义的 a 最终还是会被拿到 state 中
store.state.a // -> moduleA 的状态
store.state.b // -> moduleB 的状态

使用

<template>
  <div id="app">
    <h2>{{ $store.state.a.name }}</h2>
    <button @click="updateName">+</button>
    <h2>{{ $store.getters.fullName }}</h2>
    <h2>{{ $store.getters.fullName2 }}</h2>
    <h2>{{ $store.getters.fullName3 }}</h2>
    <button @click="asyncUpdateNmae">异步修改名字</button>
  </div>
</template>

<script>
export default {
  components: { HelloVuex },
  name: "App",
  methods: {
    updateName() {
      // 会在最外层的 mutations 查找,如果没有会在 modules 中查找
      this.$store.commit("updateName", "lisi");
    },
    asyncUpdateNmae() {
      this.$store.dispatch("upDateName");
    },
  },
};
</script>

对象的解构

ES6 中的语法

const obj = {
     name: 'tumi',
  age: 18,
  height: 1.8
}

const {name, height, age} = obj

所以 actions 也能写成另一种写法

const moduleA = {
     actions: {
       increment({ state, commit, rootState }) {
         commit('updateName', 'wanwu')
    }
  }
}

项目结构

├── index.html
├── main.js
├── api
│   └── ... # 抽取出API请求
├── components
│   ├── App.vue
│   └── ...
└── store
    ├── index.js          # 我们组装模块并导出 store 的地方
    ├── actions.js        # 根级别的 action
    ├── mutations.js      # 根级别的 mutation
    └── modules
        ├── cart.js       # 购物车模块
        └── products.js   # 产品模块

规范

  • mutations 唯一的目的就是修改 state 中的状态
  • mutations 中的每个方法尽可能完成一件事
    • 因为 devtools 跟踪显示的是方法名

image.png

  • 当 mutations 中的方法是完成多件事时,可以使用 actions 来完成