前言:本文章是本人日常所学记录,示例均为简单易懂示例,等简单示例懂了,想怎么扩展怎么扩展。若有错请指出,谢谢!
一 、Vuex理解
vuex是专为vue.js应用程序开发的状态管理机制,可以存储管理所有组件的状态,并通过相应的规则保证状态以可预测的方式放生变化。简单的理解就是一个公共仓库(store),存在store里的状态就像是一个全局对象,哪里需要就用在哪里,而在单个组件中存的状态就像是一个局部对象,只能在规定的区域内使用,但是又和单纯的全局对象不同:
1.Vuex的状态存储是响应式的。如用Vue组件从store中读取状态的时候,如果store的状态发生改变,则在Vue组件中也会相应的更新。
2.不能直接改变store中的状态。改变store中状态的唯一方法就是提交(commit)mutation
二 、为什么应用Vuex
当我们的应用遇到需要多个组件共享一个状态,或者需要兄弟之间传值时,怎么办呢?父子传值可以通过props,emit方法进行传值,这时候就需要强大的Vuex了。
三 、Vuex的流程机制

四、State
获取store中的状态
Vuex 通过 store 选项,提供了一种机制将状态从根组件“注入”到每一个子组件中(需调用 Vue.use(Vuex)),这样store实例就会注入根组件下的所有子组件中,且子组件可以通过this.$store访问到。

简单示例:
前提使用vue-cli框架
Vue组件通过$store.state.num获取store中state中的num,一般情况下不会直接使用store中state中的值,而是在计算属性(computed)中返回某个状态,因为计算属性是基于响应式以来进行缓存的,只有相关响应式依赖发生变化时才会重新求值,如果没有变化,计算属性就会立即返回之前的计算结果,而不必再次执行函数,如果直接使用store的值,即使没有变化,去一次就会执行一次函数。
<template>
<!--Vue 组件-->
<div>
<h1>$store.state.num:{{countNum}}</h1>
</div>
</template>
<script>
export default {
data() {
return {
num: 0
}
},
computed: {
countNum() {
return this.$store.state.num
}
},
created() {
console.log("this.$store.state.num:", this.$store.state.num)
},
}
</script>
store文件
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
count: 0,
num: 100,
},
})

mapState辅助函数
如上所述,当取state中的值时,每个值都需要声明计算属性,这样就变得重复和冗余了,这并不是我们想看到的,所以这时mapState辅助函数就起了大作用了。
普通使用方法:
<template>
<div>
<h1>$store.state.num:{{countNum}}</h1>
</div>
</template>
<script>
import {mapState} from 'vuex'
export default {
data(){
return{
num:0
}
},
created(){
console.log("this.$store.state.num:",this.$store.state.num)
},
computed: mapState({
//方法一
countNum:state => state.num ,
//方法二 'num'相当于state => state.num
// countNum:'num',
//方法三 为了能够使用this获取局部状态,
//必须使用常规函数,不能使用箭头函数
// countNum(state) {
// return state.num
// }
}),
methods:{
changeNum(num){}
}
}
</script>
computed: mapState([
// 方法四 映射this.num为state => state.num
//' num'
]),
高级使用方法
使用普通方法有个缺点,就是如果需要局部的计算属性怎么办呢,灵光一现,mapState函数返回的是一个对象,这样就可以通过对象展开运算符(...)将局部计算属性和mapState合并为一个对象,将最终的对象传给computed属性。

五、Getter
有时候我们需要对state中的值进行某些操作,但是有多个组件都需要相同的操作,怎么办呢,这时候可以在store中定义getter属性,getter属性可以理解为store中的计算属性,类似于组件中的computed属性,getter的返回值会根据他的依赖缓存起来,只有当依赖发生改变时才会重新计算,如果没有改变会立即返回之前计算的值,不必进行再次的计算。
例如当多个组件需要使用类似于下面操作时,可以使用getter属性
computed: {
doneTodosCount () {
return this.$store.state.todos
.filter(todo => todo.done)
.length
}
}
怎么写
Getter接受state作为第一个参数,也就是你传不传参数,他就在那。Getter也可以接受其他的getter作为第二个参数
如:
getters: {
doneTodosCount: (state, getters) => {
return getters.doneTodos.length
}
}
示例:
import Vue from 'vue'
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
user: [
{id:1,sex:'女',firstname:'wang',years:'10'},
{id:2,sex:'女',firstname:'liu',years:'17'},
{id:3,sex:'男',firstname:'wang',years:'16'},
{id:4,sex:'男',firstname:'li',years:'10'},
{id:5,sex:'女',firstname:'wang',years:'50'},
{id:6,sex:'男',firstname:'wang',years:'10'},
{id:7,sex:'女',firstname:'wang',years:'12'},
{id:8,sex:'男',firstname:'wang',years:'10'}
]
},
getters: {
// 筛选年龄小于等于17岁的用户
filteruser:state=>{ //方法一
return state.user.filter(item=>item.years <='17')
},
// 筛选制定的id 可以通过让 getter
//返回一个函数,来实现给 getter 传参。
//在对 store 里的数组进行查询时非常有用。
getUserbyId:state=>{ //方法二
return (id)=>{
return state.user.filter(item=>item.id == id)
}
}
}
})
怎么用
<template>
<div>
<table border="1">
<tr>
<th>ID</th>
<th>姓</th>
<th>性别</th>
<th>年龄</th>
</tr>
<tr v-for="(item,index) in getUserList" :key="index">
<th>{{item.id}}</th>
<th>{{item.firstname}}</th>
<th>{{item.sex}}</th>
<th>{{item.years}}</th>
</tr>
<!-- 方法二通过方法访问-->
<h1>id=2的数据:$store.getters.getUserbyId(2)</h1>
</table>
</div>
</template>
<script>
import {mapState} from 'vuex'
export default {
data() {
return {
count: 0
}
},
computed: {
getUserList() {
//方法一
return this.$store.getters.filteruser
},
}
}
</script>

需要注意:
在上述示例中,组件的中方法一是通过计算属性进行访问getter对象的,通过计算属性访问时,作为Vue的响应式系统的一部分缓存其中的,也就是所所依赖的值不变就不会重新执行相应的内容,但是getter在通过方法访问时,每次都会去进行调用,而不会有缓存结果。
mapGetter辅助函数
mapGetters 辅助函数仅仅是将 store 中的 getter 映射到局部计算属性
import { mapGetters } from 'vuex'
export default {
// ...
computed: {
// 使用对象展开运算符将 getter 混入 computed 对象中
...mapGetters([
'doneTodosCount',
'anotherGetter',
// ...
])
}
}
如果你想将一个 getter 属性另取一个名字,使用对象形式:
mapGetters({
// 把 `doneCount` 映射为
//`this.$store.getters.doneTodosCount`
doneCount: 'doneTodosCount'
})
六、Mutation
前面只是说了怎么从store获取状态,但是想要改变状态怎么办呢,更改store中的状态的唯一方法就是提交(commit)mutation。mutation其实就是类似于事件,每一个mutation都有一个字符串的事件类型(type)和一个回调函数(handler),其中回调函数就是进行状态更改的地方,并且回调函数接受state作为第一个参数。
简单示例:
vue组件:
<template>
<div>
<h2>store {{num}}</h2>
<h2>number {{number}}</h2>
<button @click="changeCount">点我呀</button>
</div>
</template>
<script>
import {mapState} from 'vuex'
export default {
data(){
return{
obj:{
d:0
},
number:1
}
},
computed:{
...mapState([
'num'
])
},
methods:{
changeCount(){
this.$store.commit("CHANGE_VALUE",this.number)
}
}
}
</script>

上面图片中提到载荷,载荷即是mutation额外的参数,但是在大多数情况下载荷应该是一个参数,这样可以包含多个字段并且记录 mutation 会更易读,如下:
changeCount(){
this.$store.commit("CHANGE_VALUE",{
num:this.number
})
}
还有另一种就是使用对象风格的提交方式,即直接使用包含type属性的对象,当使用这种方式提交时,整个对象都作为载荷传给mutation函数,回调函数保持不变
changeCount(){
this.$store.commit({
type:'CHANGE_VALUE'
num:this.number
})
}
回调函数
mutations: {
CHANGE_VALUE(state,num){
// mutations 接受state 作为第一个参数,你传不传参他都在,
state.num = state.num + num
}
},
store文件
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
count:0,
num:100,
},
mutations: {
CHANGE_VALUE(state,num){
// mutations 接受state 作为第一个参数,你传不传参他都在,
state.num = state.num + num
}
},
})

mutation还有一种写法,使用常量替代mutation事件类型,这种写法在较大的项目中让代码一目了然,更加清楚,是否需要使用根据自身的需求。
// 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
}
}
})
补充:
1、mutation必须是同步函数,在 Vuex 中,mutation 都是同步事务。
2.在组件中提交mutation可以使用mapMutations 辅助函数(一般不常用)
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')`
})
}
}
需要注意:
1、最好提前在store中初始化好所需的属性
2、在需要在对象上添加新属性时,应该使用Vue.set(oldobj,'newProp',value)或者使用新对象替换老的对象,例如,利用对象展开运算符(...)
state.obj = { ...state.obj, newProp: 123 }
vue.$set的用法
为什么要用vue.$set 在开发过程中,当vue中的data或者state里声明或者已经赋过值的对象或者数组(数组里面的值是对象)时,向对象中添加新的属性时,如果更改新属性的值时,在视图上取的新属性的值是不会改变的。
官方定义:如果在实例创建之后添加新的属性到实例上,他不会触发视图更新。
为什么不能更新后不能响应
受现在JavaScript的限制(以及废弃的Object.observe),Vue不能检测到对象属性的添加或删除。由于Vue会在初始化示例时对属性执行getter/setter转化过程,所以属性必须在data对象上存在才能让Vue转化它,这样他才能响应。
示例:


解决方法
官方定义:Vue不允许在已经创建实例上动态添加新的根级响应式属性,然而它可以使用Vue.set(object,key,value)方法将响应属性添加到嵌套的对象上:
Vue.set(vm.obj,'e',0)
也可以使用vm.$set实例方法,这也是全局Vue.set方法
this.$set(this.obj,'e',02)
如果想在已有对象上添加一些属性,例如使用Object.assign()或_.extend()方法来添加属性。但是,添加到对象撒花姑娘的新属性不会进行更新,在这种情况下可以创建一个新的对象,让他包含原对象的属性和新的属性:
this.obj = Object.assign({},this.obj,{a:1,e:2})
示例:


七、Action
action类似于mutation,但不同在于
1、Action提交的是mutation,不是直接改状态
2、Action可以包含任意异步操作
简单示例
<template>
<div>
<h2>store {{num}}</h2>
<h2>number {{number}}</h2>
<button @click="changeCount">点我呀</button>
</div>
</template>
<script>
import {mapState} from 'vuex'
export default {
data(){
return{
number:1
}
},
methods:{
changeCount(){
// Action 通过 store.dispatch 方法触发:
this.$store.dispatch('CHANGE_VALUE_ACTION',this.number)
}
}
}
</script>
store文件
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
num:100,
},
mutations: {
CHANGE_VALUE(state,num){
// mutations 接受state 作为第一个参数,你传不传参他都在,
state.num = state.num + num
}
},
actions: {
// 写法一
CHANGE_VALUE_ACTION(context,num){
console.log("num",num)
// Action 函数接受一个与 store 实例具有相同方法和属性的 context 对象
// 所以context对象并不是传进来的参数,而是本身就存在
context.commit('CHANGE_VALUE',num)
}
//写法二根据参数解构
// CHANGE_VALUE_ACTION({commit,state},num){
// num为载荷
// commit('CHANGE_VALUE',num)
// }
},
})
action同样支持载荷方式和对象方式进行分发
// 以载荷形式分发
store.dispatch('incrementAsync', {
amount: 10
})
// 以对象形式分发
store.dispatch({
type: 'incrementAsync',
amount: 10
})
可以查看官网的购物车示例,,涉及到调用异步 API 和分发多重 mutation:
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)
)
}
}
注意我们正在进行一系列的异步操作,并且通过提交 mutation 来记录 action 产生的副作用(即状态变更)