浅谈Vuex

226 阅读6分钟

前言:本文章是本人日常所学记录,示例均为简单易懂示例,等简单示例懂了,想怎么扩展怎么扩展。若有错请指出,谢谢!

一 、Vuex理解

vuex是专为vue.js应用程序开发的状态管理机制,可以存储管理所有组件的状态,并通过相应的规则保证状态以可预测的方式放生变化。简单的理解就是一个公共仓库(store),存在store里的状态就像是一个全局对象,哪里需要就用在哪里,而在单个组件中存的状态就像是一个局部对象,只能在规定的区域内使用,但是又和单纯的全局对象不同:

1.Vuex的状态存储是响应式的。如用Vue组件从store中读取状态的时候,如果store的状态发生改变,则在Vue组件中也会相应的更新。

2.不能直接改变store中的状态。改变store中状态的唯一方法就是提交(commit)mutation

二 、为什么应用Vuex

当我们的应用遇到需要多个组件共享一个状态,或者需要兄弟之间传值时,怎么办呢?父子传值可以通过props,emit方法进行传值,这时候就需要强大的Vuex了。

三 、Vuex的流程机制

Vue Components就是表示组件,可以通过dispath派送actions,但是这一步不是必须的,可以通过action进行提交(commit)mutations来改变state的值,也可以在组件中直接commit mutations来改变state的值。

四、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 产生的副作用(即状态变更)