vuex的基础使用

本文深入解析Vuex状态管理模式,涵盖store、getter、mutation及action核心概念,展示如何通过响应式状态更新优化Vue组件,提供实用代码示例。

Vuex 应用的核心就是 store,含着大部分的状态 (state)

Vuex 和单纯的全局对象有以下两点不同:

  1. Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。

  2. 不能直接改变 store 中的状态。改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation

一、stroe,Vuex 使用的单一状态树,用一个对象就包含了全部的应用层级状态,作为一个“唯一数据源",每个应用将仅仅包含一个 store 实例

1、新建文件夹src/store/index.js

import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(vuex)
const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment (state) {
      state.count++
    }
  }
})
export default store;

 2、main.js

import Vue from 'vue'
import store from './store/index'

new Vue({
  store, //把 store 的实例注入所有的子组件
  render: h => h(App)
}).$mount('#app')

3、在页面中获取state数据

由于 Vuex 的状态存储是响应式的,从 store 实例中读取状态最简单的方法就是在计算属性 中返回某个状态,也可以直接在页面中使用this.$store.state来获取定义的数据。

<template>
  <div>
    <div>{{this.$store.state.count}}</div>
    <div>{{count1}}</div>
  </div>
</template>

<script>
export default {
  data () {
    return {}
  },
  computed: {
    count1: function () {
      return this.$store.state.count
    }
  }
}
</script>

4、mapState辅助函数

当一个组件需要获取多个状态时候,将这些状态都声明为计算属性会有些重复和冗余。为了解决这个问题,我们可以使用 mapState 辅助函数帮助我们生成计算属性

<template>
  <div>
    <div>{{count}}</div>
    <div>{{allCount}}</div>
  </div>
</template>

<script>
import { mapState } from 'vuex'
export default {
  data () {
    return {
      num: 5
    }
  },
  computed: mapState({
    count: state => state.count,
    allCount: function (state) { // 为了能够使用 `this` 获取局部状态num,必须使用常规函数
      return state.count + this.num
    }
  })
}
</script>

上面的方法不方便的是不能与局部计算属性混用,mapState 函数返回的是一个对象,所以我们可以使用展开运算符

<template>
  <div>
    <div>{{count}}</div>
    <div>{{allCount}}</div>
  </div>
</template>

<script>
import { mapState } from 'vuex'
export default {
  data () {
    return {
      num: 5
    }
  },
  computed: {
    ...mapState({
      count: state => state.count,
      allCount: function (state) {
        return state.count + this.num
      }
    })
  }
}
</script>

而且当映射的计算属性的名称与 state 的子节点名称相同时,我们也可以给 mapState 传一个字符串数组

<template>
  <div>
    <div>{{count}}</div>
  </div>
</template>

<script>
import { mapState } from 'vuex'
export default {
  data () {
    return {}
  },
  computed: {
    ...mapState(['count'])
  }
}
</script>

二、getter

Vuex 允许我们在 store 中定义“getter”(可以认为是 store 的计算属性)。就像计算属性一样,getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。

Getter 接受 state 作为其第一个参数,也可以接受其他 getter 作为第二个参数

1、store/index.js

import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(vuex)
const store = new Vuex.Store({
  state: {
    count: 0
  },
  getters: {
    twoCount: state => state * 2,
    todoLen: function(state,getters){
      return getters.twoCount
    }
  }
})
export default store;

2、页面中,Getter 会暴露为 store.getters 对象,在页面中访问this.$store.getters,也可以在计算属性种返回:

<template>
  <div>
    <div>{{this.$store.getters.twoCount}}</div>
    <div>{{getterTodoLen}}</div>
  </div>
</template>

<script>
export default {
  data () {
    return {}
  },
  computed: {
    getterTodoLen: function() {
      return this.$store.getters.todoLen
    }
  }
}
</script>

3、mapGetters辅助函数

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

使用对象展开运算符将 getter 混入 computed 对象中

<template>
  <div>
    <div>{{twoCount}}</div>
    <div>{{todoLen}}</div>
  </div>
</template>

<script>
import { mapGetters } from 'vuex'
export default {
  data () {
    return {}
  },
  computed: {
    ...mapGetters(['twoCount', 'todoLen'])
  }
}
</script>

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

把 `this.XXX` 映射为 `this.$store.getters.XXX`

<template>
  <div>
    <div>{{twoCountName}}</div>
    <div>{{todoLenName}}</div>
  </div>
</template>

<script>
import { mapGetters } from 'vuex'
export default {
  data () {
    return {}
  },
  computed: {
    ...mapGetters({
      twoCountName: 'twoCount',
      todoLenName: 'todoLen'
    })
  }
}
</script>

三、Mutation, 更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。Vuex 中的 mutation 非常类似于事件,它会接受 state 作为第一个参数,也可以提交额外的参数,即 mutation 的 载荷(payload)

1、store/index.js

import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(vuex)
const store = new Vuex.Store({
  state: {
    count: 0
  },
  getters: {
    twoCount: state => state * 2,
    todoLen: function(state,getters){
      return getters.twoCount
    }
  },
  mutations: {
    addNum (state) {
      state.count = state.count + 2;
    },
    reduce (state, n) {
      state.count = state.count - n;
    }
  }
})
export default store;

2、在页面中,在组件中使用this.$store.commit('xxx') 提交 mutation

<template>
  <div>
    <div>{{twoCount}}</div>
    <div>{{todoLen}}</div>
    <button @click="add">增加</button><button  @click="reduce">减少</button>
  </div>
</template>

<script>
import { mapGetters } from 'vuex'
export default {
  data () {
    return {}
  },
  computed: {
    ...mapGetters(['twoCount','todoLen'])
  },
  methods: {
    add: function() {
      // 触发mutations里addNum事件,改变count
      this.$store.commit('addNum')
    },
    reduce: function() {
      // 触发mutations里reduceNum事件,改变count
      let n = 3;
      this.$store.commit('reduceNum', n); // n为额外的参数,即载荷
    }
  }
}
</script>

mutation 的 载荷(payload)也可以是一个对象,包含多个字段

reduce: function() {
      // 触发mutations里reduceNum事件,改变count
      let params = {
        num: 3,
        age: 20
      };
      this.$store.commit('reduceNum', params)
    }
reduceNum (state, params) {
      state.count = state.count - params.num;
    }

Mutation的注意事项:

  1. 最好提前在你的 store 中初始化好所有所需属性。

  2. 当需要在对象上添加新属性时,你应该

    使用 Vue.set(obj, 'newProp', 123)
  3. 以新对象替换老对象。例如,利用对象展开运算符 我们可以这样写:

          state.obj = { ...state.obj, newProp: 123 }

3、使用常量替代 Mutation 事件类型

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

新建store/mutation-types.js

export const SET_COUNT = 'SET_COUNT'

store/index.js,使用 ES2015 风格的计算属性命名功能来使用一个常量作为函数名

import Vue from 'vue'
import Vuex from 'vuex'
import SET_COUNT from './mutation_type'
Vue.use(vuex)
const store = new Vuex.Store({
  state: {
    count: 0
  },
  getters: {
    twoCount: state => state * 2,
    todoLen: function(state,getters){
      return getters.twoCount
    }
  },
  mutations: {
    addNum (state) {
      state.count = state.count + 2;
    },
    reduceNum (state, params) {
      state.count = state.count - params.num;
    },
    [SET_COUNT] (state) {
      return state.count
    }
  }
})

在页面中

add: function() {
      this.$store.commit('SET_COUNT')
    },

如果你不喜欢,你完全可以不这样做。

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

4、mapMutations辅助函数

在组件中除了使用 this.$store.commit('xxx') 提交 mutation,也可以使用 mapMutations 辅助函数将组件中的 methods 映射为 store.commit 调用

<template>
  <div>
    <div>{{twoCount}}</div>
    <div>{{todoLen}}</div>
    <button @click="add">增加</button><button  @click="reduce">减少</button>
  </div>
</template>

<script>
import { mapGetters, mapMutations } from 'vuex'
export default {
  data () {
    return {}
  },
  computed: {
    ...mapGetters(['twoCount','todoLen'])
  },
  methods: {
    // 将 `this.addNum()` 映射为 `this.$store.commit('addNum')`
    // 使用字符串数组,把mutations里的方法映射为本地组件方法
    ...mapMutations(['addNum', 'reduceNum']),
    add (){
      this.addNum()
    },
    reduce () {
      let n = 3;
      this.reduceNum(n);
    }
  }
}
</script>

也可以自定义修改mutaions里的方法在本地组件中的名字

methods: {
    // 将 `this.addNum()` 映射为 `this.$store.commit('addNum')`
    // 使用字符串数组,把mutations里的方法映射为本地组件方法
    ...mapMutations({
      addN: 'addNum',
      reduceN: 'reduceNum'
    }),
    add (){
      this.addN()
    },
    reduce () {
      let n = 3;
      this.reduceN(n);
    }
  }

四、Action,Action 函数接受一个与 store 实例具有相同方法和属性的 context 对象,因此你可以调用 context.commit 提交一个 mutation,或者通过 context.statecontext.getters 来获取 state 和 getters。

Action 类似于 mutation,不同在于:

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

官方建议我们不直接通过mutation改变state,而是先提交actions,在actions里提交mutaion,再去修改state

1、store/index.js

在actions里定义提交mutaions的函数

import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(vuex)
const store = new Vuex.Store({
  state: {
    count: 0
  },
  getters: {
    twoCount: state => state * 2,
    todoLen: function(state,getters){
      return getters.twoCount
    }
  },
  actions: {
    addNum(context){
      context.commit('addNum')
    },
    reduceNum(context){
      context.commit('reduceNum')
    }
  },
  mutations: {
    addNum (state) {
      state.count = state.count + 2;
    },
    reduceNum (state, params) {
      state.count = state.count - params.num;
    }
  }
})
export default store;

实践中,我们会经常用到 ES2015 的 参数解构 来简化代码(特别是我们需要调用 commit 很多次的时候)

actions: {
    addNum({commit, state}){
      commit('addNum')
    },
    reduceNum({commit}){
      commit('reduceNum')
    }
  },

而且,可以在actions方法里面执行异步操作

actions: {
    addNum({commit}){
      setTimeout(()=>{
        commit('addNum')
      },1000)
    },
    reduceNum({commit}){
      commit('reduceNum')
    }
  },

 

2、在页面中,通过store.dispatch('addNum')触发方法

<template>
  <div>
    <div>{{twoCount}}</div>
    <div>{{todoLen}}</div>
    <button @click="add">增加</button><button  @click="reduce">减少</button>
  </div>
</template>

<script>
import { mapGetters, mapMutations } from 'vuex'
export default {
  data () {
    return {}
  },
  computed: {
    ...mapGetters(['twoCount','todoLen'])
  },
  methods: {
    add (){
      this.$store.dispatch('addNum')
    },
    reduce () {
      this.$store.dispatch('reduceNum')
    }
  }
}
</script>

Actions 支持同样的载荷方式和对象,即actions可以传第二个参数,即额外的参数

页面中

<template>
  <div>
    <div>{{twoCountName}}</div>
    <div>{{todoLenName}}</div>
    <button @click="add">增加</button><button  @click="reduce">减少</button>
  </div>
</template>

<script>
import { mapGetters, mapMutations } from 'vuex'
export default {
  data () {
    return {}
  },
  computed: {
    ...mapGetters({
      twoCountName: 'twoCount',
      todoLenName: 'todoLen'
    })
  },
  methods: {
    add (){
      let n = 3;
      this.$store.dispatch('addNum', n)
    },
    reduce () {
      let params = {
        num : 2
      }
      this.$store.dispatch('reduceNum', params)
    }
  }
}
</script>

store/index.js

import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(vuex)
const store = new Vuex.Store({
  state: {
    count: 0
  },
  getters: {
    twoCount: state => state * 2,
    todoLen: function(state,getters){
      return getters.twoCount
    }
  },
  actions: {
    addNum({commit}, n){
      setTimeout(()=>{
        commit('addNum', n)
      },1000)
    },
    reduceNum({commit}, params){
      commit('reduceNum', params)
    }
  },
  mutations: {
    addNum (state, n) {
      state.count = state.count + n;
    },
    reduceNum (state, params) {
      state.count = state.count - params.num;
    }
  }
})
export default store;

3、mapActions辅助函数

在组件中可以使用 this.$store.dispatch('xxx') 分发 action,也可以使用 mapActions 辅助函数将组件的 methods 映射为 store.dispatch 调用,将actions方法映射为本地方法

import { mapGetters, mapMutations, mapActions } from 'vuex'
export default {
  data () {
    return {}
  },
  computed: {
    ...mapGetters({
      twoCountName: 'twoCount',
      todoLenName: 'todoLen'
    })
  },
  methods: {
    // 将 `this.addNum()` 映射为 `this.$store.dispatch('addNum')`
    ...mapActions(['addNum', 'reduceNum']),
    add (){
      let n = 3;
      this.addNum(n) // 传参
    },
    reduce () {
      let params = {
        num : 2
      }
      this.reduceNum(params) // 传参
    }
  }
}
</script>

 

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

LLL_LH

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值