1. 介绍

Vuex是Vue官方推出的一个状态管理工具,其能生成一个单独的全局状态实例,其有以下特点:

  • 能够在vuex中集中管理共享的数据,利于开发和后期的维护
  • 能够在Vue的各个组件中实现访问和共享,能够有效解决以下兄弟组件、祖孙组件等跨代组件的通信困难问题。
  • 存储在 vuex 中的数据都是响应式的,能够实时保持数据与页面的同步。

2. 安装

//CDN
https://unpkg.com/[email protected]
//NPM
npm i vuex@ 3.6.2 -s

注意事项:版本号在3.6.2以下的vuex适用于vue2,若在vue2的工程项目中直接npm i vuex -s会报如下的错误

解决办法是指定版本号,目前vue2使用的版本目前最高支持到3.6.2,具体可在更新记录中进行查阅。

3.核心概念

3.1 State

State相对于vuex来说就好比vue组件中的data,所以声明state必须是纯粹的对象 (含有零个或多个的key/value对),在建立后,State数据会被转换为响应式的数据。

3.1.1 State的访问有以下的三种形式

  • 在vue组件中(tempaltedatamethods、声明周期函数等)中访问
  • 在vuex中访问
  • 在其他js文件中访问

首先定义一个store,里面只包含State,然后我们将其注入到全局,下面一次进行以上三种形式的测试

//store.js
import Vue from "vue";
import Vuex from "vuex"
Vue.use(Vuex)

const store = new Vuex.Store({
    state: {
        userName: "Vuex的学习之旅"
    }
})

export default store
//main.js
import Vue from 'vue'
import App from './App.vue'
import store from "./store"

Vue.config.productionTip = false

new Vue({
  render: h => h(App),
  store
}).$mount('#app')
//App.vue
<template>
  <div id="app">
    <test-component></test-component>
  </div>
</template>

<script>
import TestComponent from "./components/test-component.vue"
export default {
  name: "app",
  components: { TestComponent }
}
</script>

1.在vue组件中访问

  • <template>直接在模板中使用$store.state...进行store中state数据的访问
  • <script>通过this.$store.state...进行store中state数据的访问

    //test-component
    <template>
      <div id="app">
          <div>姓名:{{ $store.state.userName }}</div>
      </div>
    </template>
    
    <script>
    export default {
      name: 'test',
      data() {
          return {
              username: this.$store.state.userName
          }
      },
      created() {
          console.log(this.$store.state.userName);
          console.log(this.username);
      }
    }
    </script>

    注:由于不能修改store数据,所以一般不建议在data中访问store的数据,因为它没有缓存效果,一般在计算属性中获取state数据。

    //test-component
    <template>
      <div id="app">
          <div>姓名:{{ compute_username }}</div>
          <div>{{ compute_age }}</div>
          <div>{{ compute_sex }}</div>
      </div>
    </template>
    
    <script>
    export default {
      name: 'test',
      data() {
          return {
              description: "性别是"
          }
      },
      computed: {
          compute_username() {
              return this.$store.state.userName
          },
          compute_age() {
              return "年龄" + this.$store.state.age
          },
          compute_sex() {
              return this.description + this.$store.state.sex
          }
      }
    }
    </script>

  1. 在vuex中访问
    如果vuex中访问state的数据,一般是在mutationgetters中可以通过this.state...进行获取

    //store.js
    import Vue from "vue";
    import Vuex from "vuex"
    Vue.use(Vuex)
    
    const store = new Vuex.Store({
     state: {
         userName: "Vuex的学习之旅"
     },
     mutations: {
         set_userName(state, data) {
             this.state.userName = data
             //等同于 state.userName = data
         }
     }
    })
    export default store
    //test-component
    <template>
     <div id="app">
         <div>姓名:{{ $store.state.userName }}</div>
     </div>
    </template>
    
    <script>
    export default {
     name: 'test',
     data() {
         return {
             username: this.$store.state.userName
         }
     },
     created() {
         this.$store.commit("set_userName", "修改了vuex的名字")
     }
    }
    </script>
  2. 在其他js文件中访问
    在其它js文件中访问就需要先行引入store.js,然后对这个store.js文件导出的对象进行取值等操作。

    // other.js
    import store from "./store"
    console.log(store.state.userName);

3.1.2 mapState语法糖

mapState 语法糖的主要作用是将store中的state映射到当前vue实例的computed中

在没有mapState时,我们获取state一般在computed中进行获取,对于获取多个state数据,那么我们就要写多个computed方法,如下

//store.js
import Vue from "vue";
import Vuex from "vuex"
Vue.use(Vuex)

const store = new Vuex.Store({
    state: {
        userName: "Vuex的学习之旅",
        age: 18,
        sex: "男"
    }
})
export default store
//test-component
<template>
    <div id="app">
        <div>姓名:{{ compute_username }}</div>
        <div>{{ compute_age }}</div>
        <div>{{ compute_sex }}</div>
    </div>
</template>

<script>
export default {
    name: 'test',
    data() {
        return {
            description: "性别是"
        }
    },
    computed: {
        compute_username() {
            return this.$store.state.userName
        },
        compute_age() {
            return "年龄" + this.$store.state.age
        },
        compute_sex() {
            return this.description + this.$store.state.sex
        }
    }
}
</script>

针对于写多个computed的繁琐和冗余,使用mapState可以帮助我们生成计算属性

3.1.2.1 mapState对象写法

//test-component
<template>
    <div id="app">
        <div>姓名:{{ compute_username }}</div>
        <div>{{ compute_age }}</div>
        <div>{{ compute_sex }}</div>
    </div>
</template>

<script>
import { mapState } from 'vuex'
export default {
    name: 'test',
    data() {
        return {
            description: "性别是"
        }
    },
    computed: mapState({
        compute_username(state) {
            return state.userName
            //等同于return this.$store.state.userName
        },
        //ES6箭头函数
        //compute_username:state => state.userName
        //compute_username:state => this.$store.state.userName


        // 传字符串参数 'age' 等同于 `state => state.age`
        compute_age: 'age',

        //需要配合组件实例中的其它数据,使用普通函数的形式,才能保证this的指向
        compute_sex(state) {
            return this.description + state.sex
        }
    })
}
</script>

3.1.2.1 mapState数组写法

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

<template>
    <div id="app">
        <div>姓名:{{ userName }}</div>
        <div>{{ age }}</div>
        <div>{{ sex }}</div>
    </div>
</template>

<script>
import { mapState } from 'vuex'
export default {
    name: 'test',
    data() {
        return {
            description: "性别是"
        }
    },
    computed: {
        userName() {
            return this.$store.state.userName
        },
        age() {
            return this.$store.state.age
        },
        sex() {
            return this.$store.state.sex
        }
    }
}
</script>
<template>
    <div id="app">
        <div>姓名:{{ userName }}</div>
        <div>{{ age }}</div>
        <div>{{ sex }}</div>
    </div>
</template>

<script>
import { mapState } from 'vuex'
export default {
    name: 'test',
    data() {
        return {
            description: "性别是"
        }
    },
    computed: mapState(["userName", "age", "sex"])
}
</script>

3.1.2.2 mapState使用剩余展开运算符

mapState返回的是一个对象,如果computed只包括mapState,那么直接写

 computed: mapState(["userName", "age", "sex"])
// 等同于
computed: {
      ...mapState(["userName", "age", "sex"])
}

如果你的computed还有其它store之外的属性,那么你需要使用剩余运算符,把mapState返回的对象和其它计算属性对象合并

<template>
    <div id="app">
        <div>姓名:{{ userName }}</div>
        <div>{{ age }}</div>
        <div>{{ sex }}</div>
        <div>{{ getSexEnglishType }}</div>
    </div>
</template>

<script>
import { mapState } from 'vuex'
export default {
    name: 'test',
    data() {
        return {
            description: "性别是"
        }
    },
    computed: {
        ...mapState(["userName", "age", "sex"]),
        getSexEnglishType() {
            return this.sex == "男" ? "boy" : "girl"
        }
    }
}
</script>

4. getters

顾名思义,getters就好比是store的计算属性computed,只有当store中的依赖发生了改变,才会重新触发getters。

import Vue from "vue";
import Vuex from "vuex"
Vue.use(Vuex)
const store = new Vuex.Store({
    state: {
        carList: [{
            type: "公交车",
            count: 1
        }, {
            type: "汽车",
            count: 3
        }, {
            type: "出租车",
            count: 5
        }, {
            type: "卡车",
            count: 7
        }]
    },
    getters: {
        // getCars(state){
        //     return state.carList.filter(car=>car.count>4)
        // }
        getCars: state => state.carList.filter(car => car.count > 4)
    }
})
export default store

4.1 getters的访问

同state获取的方式一样,getters的获取分为以下三种

  • 在vue组件中(tempaltedatamethods、声明周期函数等)中访问
  • 在vuex中访问
  • 在其他js文件中访问

其三类的实现逻辑与state相近,这里只介绍在vue组件中访问

4.1.1在组件中通过属性访问

通过属性访问就是直接获取store的getters,再点出具体的获取属性即可

// test-component.vue
<script>
import { mapGetters } from 'vuex'
export default {
    name: 'test',
    data() {
        return {
            description: "性别是"
        }
    },
    computed: {
        getCars() {
            return this.$store.getters.getCars
        }
    }
}
</script>

getters第一个参数是state,第二个参数是getters,那么我们就可以在一个getters方法中使用第二个参数嵌套使用其他的getters

//store.js
import Vue from "vue";
import Vuex from "vuex"
Vue.use(Vuex)

const store = new Vuex.Store({
    state: {
        carList: [{
            type: "公交车",
            count: 1
        }, {
            type: "汽车",
            count: 3
        }, {
            type: "出租车",
            count: 5
        }, {
            type: "卡车",
            count: 7
        }]
    },
    getters: {
        // getCars(state){
        //     return state.carList.filter(car=>car.count>4)
        // }
        getCars: (state,getters) => getters.formateCars,
        formateCars(state,getters){
            return state.carList.filter(car => car.count > 4)
        }
    }
})
export default store
// test-component.vue
<template>
    <div id="app">
        <div>车辆:{{ getCars }}</div>
    </div>
</template>

<script>
import { mapGetters } from 'vuex'
export default {
    name: 'test',
    computed: {
        getCars(state,getters) {
            return this.$store.getters.getCars
        },

    }
}
</script>

4.1.2 通过方法访问

getter像computed方法一样,可以利用返回的匿名函数实现参数的传递,但是这样会导致缓存的作用消失,当然computed如果也是使用返回的匿名函数的方式的调用,那么computed的缓存效果也会消失。

import Vue from "vue";
import Vuex from "vuex"
Vue.use(Vuex)

const store = new Vuex.Store({
    state: {
        carList: [{
            type: "公交车",
            count: 1
        }, {
            type: "汽车",
            count: 3
        }, {
            type: "出租车",
            count: 5
        }, {
            type: "卡车",
            count: 7
        }]
    },
    getters: {
        getCars: (state,getters) => count => state.carList.filter(car=>car.count == count)
    }
})
export default store
// test-component.vue
<template>
    <div id="app">
        <div>车辆:{{ getCars(5) }}</div>
    </div>
</template>

<script>
export default {
    name: 'test',
    computed: {
        getCars(state,getters) {
            return this.$store.getters.getCars
        },

    }
}
</script>

4.2 mapGetters语法糖

mapState的语法糖一样,mapGetters语法糖也分为对象形式和数组形式,此处就不再赘述。

5. Mutation

mutation是唯一能够更改Vuex中store的state状态的唯一途径,mutation非常类似于一个回调事件,其键名为字符串的事件类型,其值是一个回调函数,回调函数就是我们修改state的地方,回调函数的第一个参数是state,第二个是可选参数,他是我们调用的时候所传入的参数

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

mutation是一个回调事件,所以我们不能直接进行调用,而正确的调用方法是触发这个事件的名字

store.commit('add')

5.1 commit参数

store在commit可以传入参数,其参数分为以下两种形式

  • 载荷形式
  • 对象形式
  1. 载荷形式

    const store = new Vuex.Store({
      state: {
     count: 1
      },
      mutations: {
     add (state,data) {
       // 变更状态
       state.count += data.count
     }
      }
    })
    
    store.commit('increment', {
      count: 10
    })
    const store = new Vuex.Store({
      state: {
     count: 1
      },
      mutations: {
     add (state,count) {
       // 变更状态
       state.count += data.count
     }
      }
    })
    store.commit('increment',10)
  2. 对象形式
    对象形式是指直接使用包含type属性的对象,当然以这种形式commit所对应的函数的第二个参数肯定是一个对象的类型。

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

5.2 mutation修改store的正确方式

同vue的data一样,如果是对象形式的data,如果我们在对象类型的state上需要增添我们的新属性的时候可以采用如下方案

  1. Vue.set(obj, 'newProp', 123)
  2. 利用展开运算符合并一个新对象

    state.obj = { ...state.obj, newProp: 123 }
  3. 直接让state中的数据等一一个新对象,再使用vue.forceupdatevue.forceupdate(偏方,会强制刷新页面,损失页面性能)

5.3 使用常量type来作为mutation属性的键名

使用常量替代 mutation 事件类型在各种,同时把这些常量放在单独的文件中可以让你的代码合作者对整个 app 包含的 mutation 一目了然,
具体而言,store.commit('add'),可以发现,这里 commit 提交的方法 add,是以字符串的形式代入的。如果项目小,一个人开发的话倒还好,但是项目大了,编写代码的人多了,那就麻烦了,因为需要 commit 的方法一多,就会显得特别混乱,而且以字符串形式代入的话,一旦出了错,很难排查。

// mutation-types.js
export const SOME_MUTATION = 'SOME_MUTATION'
// store.js
import Vuex from 'vuex'
import { SOME_MUTATION } from './mutation-types'

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

5.4 Mutation 必须是同步函数

为什么Mutation 必须是同步函数呢,这本质是因为它是一个回调函数,回调函数在异步的时候无法得知哪个先完成哪个后完成,比如

foreach(async () => {
        await ...
})

因此,这样会导致一个结果,就是state中的数据不知道什么时候发生的改变的(不过也确实是改变了的),所以state不能使用异步函数,这样会丢失了其可追踪性。

5.5 Mutation的提交

mutation的提交有两种方式

  • 组件中直接通过store.commit(type)提交
  • 通过mapMutations辅助函数提交

5.5.1 组件直接提交

import Vue from "vue";
import Vuex from "vuex"
Vue.use(Vuex)

const store = new Vuex.Store({
    state: {
        count: 1
    },
    mutations:{
        add(state){
            state.count += 1
        }
    }
})
export default store
<template>
    <div id="app">
        <div>车辆:{{ $store.state.count }}</div>
        <button @click="$store.commit('add')">增加</button>
    </div>
</template>

<script>
export default {
    name: 'test'
}
</script>

<template>
    <div id="app">
        <div>车辆:{{ $store.state.count }}</div>
        <button @click="add">增加</button>
    </div>
</template>

<script>
export default {
    name: 'test',
    methods:{
        add(){
            this.$store.commit('add')
        }
    }
}
</script>

5.5.2 mapMutations语法糖

mapMutations可以将store中的commit(不是mutation)方法映射为当前vue组件中的方法,其有两种方式

  1. 数组形式的映射
  2. 对象形式的映射

5.5.2.1 mapMutations数组形式的映射

数组形式的映射很简单,就把commit的字符串参数映射为当前组件的方法,它们的名称是一致的,那么就可以通过数组进行映射。

import { mapMutations } from 'vuex'

export default {
  // ...
  methods: {
    ...mapMutations([
      'increment', // 将 `this.increment()` 映射为 `this.$store.commit('increment')`
      // `mapMutations` 也支持载荷:
      'incrementBy' // 将 `this.incrementBy(amount)` 映射为 `this.$store.commit('incrementBy', amount)`
    ])
  }
}

5.5.2.1 mapMutations对象形式的映射

对象形式的映射可以把commit的mutation名称映射为当前组件的其它方法名称

import { mapMutations } from 'vuex'

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

      // `mapMutations` 也支持载荷:
      'incrementBy' // 将 `this.incrementBy(amount)` 映射为 `this.$store.commit('incrementBy', amount)`
    ]),
    ...mapMutations({
      add: 'increment' // 将 `this.add()` 映射为 `this.$store.commit('increment')`
    })
  }
}
03-05 21:07