阅读 143

Vuex系列(一) -- Vuex的使用

这是我参与更文挑战的第10天,活动详情查看: 更文挑战

介绍

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。官方文档

vuex.png

注意: 并不所项目都需要Vuex,只有vue没有办法解决情况下或者是存储一些被认为是Vue应用中全局变量数据域时你才需要使用Vuex。滥用Vuex会导致全局数据污染不利于维护等问题。

VueX配置

//文件路径 /src/store/index.js

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)  // 将vuex 的提供的配置原型方法注册到 Vue中

export default new Vuex.Store({ // 实例化 Vuex.Store对象 并公开出去
    state: {
        value'存放在vuex store中的全局状态'
    }
})
复制代码
// main.js Vue应用入口文件将 store 配置到Vue实例对象中

import Vue from 'vue'
import store from './store' // 引入vuex 的store对象
import App from './App.vue'

Vue.config.productionTip = false

new Vue({
  store, // 将vuex 的 store配置到vue实例中 
  renderh => h(App),
}).$mount('#app')
复制代码

Vuex的核心概念

  • State

  • Getters

  • Mutations

  • Actions

  • Modules

温馨提示

// 使用辅助函数前必须导入
import { mapState , mapGetters, mapMutations, mapActions } from 'vuex';
复制代码

State

概念是Vuex用来存储数据的地方,存储在state中数据可以看做是当前应用的全局变量可以在当前应用的任何地方访问。

// src/store/index.js

export default new Vuex.Store({
  state: {
    book: 'HTML+CSS',
  }
})
复制代码

语法

  1. 在vue的任何组件中都可以使用this.$store.state.属性名访问
let store = new Vuex.Store({
    state: {
        value'存放在vuex store中的全局状态value'
    }
})

let vm = new Vue({
    el: '#app',
    store 
})
vm.$store.state.value // '存放在vuex store中的全局状态value'
复制代码
  1. 在开发中我们推荐将store中的state赋值给需要使用该状态的组件的计算属性中(一定不能把state赋值给data,state发生改变时不会重新给data赋值)
export default {
    data() {
        return {
            // 错误state.book 发生改变时book不会更新
            myBookthis.$store.state.book 
        }
    },
    computed: {
        book() {
             // 正确 state.book会作为当前计算属性的依赖,当state.book发生改变时计算属性book将会重新计算当前值
            return this.$store.state.book
        }
    }
}
复制代码
  1. Vuex 为了简化 state与计算属性配合使用时的代码,提供了一个辅助函数mapState 可以简化上面的写法
<template>
    <div>{{book}}</div>
</template>

<script>
// 首先引入辅助函数mapState
import {mapState} from 'vuex'

export default {
    /*
    computed: {
        book() {
          return this.$store.state.book;
        },
    }*/
    
    // 下面的写法等价于上面的写法
    computed: {
        ...mapState(['book'])
    }
}
</script>
复制代码

Getters

概念 getter就是Vuex的计算属性,开发人员可以将state 或其他getter 计算后的的返回值存放在指定getter中,当前getter会将这些依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算

在任何组件中都可以通过this.$store.getter.属性名访问

// src/store/index.js

export default new Vuex.Store({
  state: {
    score: [30, 20, 80, 10, 9, 66],
  },
  getters: {
    // 可以认为是 store 的计算属性, 就像计算属性一样,getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。
    calcScore(state) {
      // p(state, getters, rootState, rootGetters)
      //state: 当前模块的state
      //getters: 当前模块的getters
      //rootState: 全局的state
      //rootGetters: 全局的getters
      let ps = state.score.filter(v => {
        return v >= 30;
      })
      return ps.join('-');
    }
  },
})
复制代码

语法 getter会接收一个函数作为参数,该函内部有多个参数,并返回当前getter的值

  • 参数一 state    所有的state
    复制代码
  • 参数二 getters  所有getter
    复制代码
  1. getter在组件中依然存放在组建的计算属性中
<template>
    <div>{{score}}</div>
</template>

<script>
export default {
    computed: {
        score() {
          return this.$store.getters.calcScore;
        }
    }
}
</script>
复制代码
  1. Vuex为 getters 同样提供了辅助函数 mapGetters
  • 方法一: mapState可以接收一个字符串数组作为参数,数组中的每一项字符串都会成为当前组件的计算属性并且与Vuex中的同名getter建立映射对应关系。
<template>
    <div>{{calcScore}}</div>
</template>

<script>
// 首先引入辅助函数mapGetters
import {mapGetters} from 'vuex'

export default {
    /*
    computed: {
        book() {
          return this.$store.getters.calcScore;
        },
    }*/
    
    // 下面的写法等价于上面的写法
    computed: {
       ...mapGetters(["calcScore"])
    }
}
</script>
复制代码
  • 方法二: mapGetters可以接收对象作为参数,对象的每一个key都会成为当前组件的计算属性名,value必须是一个字符串并且与Vuex中的同名getter建立映射对应关系。
<template>
   <div>{{s1}}</div>
</template>

<script>
import {mapGetters} from 'vuex'
export default {
    computed: {
        ...mapGetters({
            s1'calcScore'
        })
    }
}
</script>
复制代码

Mutations

概念vuex规定mutation是唯一可以修改state的地方

export default new Vuex.Store({
  state: {
    book: 'HTML+CSS',
  },
  mutations: { // 修改state的方法
    // 同步操作 直接修改
    changeBook(state, book) {
      state.book = book;
    },
  },
})
复制代码

语法在vuex中通过配置选项mutations创建并使用 $store.commit方法提交mutation

1、在组件中必须使用$store.commit方法提交指定mutation,指定mutation才会触发

<template>
    <div>
        <button @click="changeBookByCom">点我更换书籍(mutations)</button>
    </div>
</template>
<script>
export default {
    methods: {
        // 执行该方法会将state中的book变成JavaScript,而不是最开始的HTML+CSS
        changeBookByCommit() {
            // 触发mutation必须使用$store所提供的commit方法提交一次mutation
            // commit 方法接收两个参数 参数一 需要触发mutation的同名字符串 参数二 载荷
            this.$store.commit("changeBook", "JavaScript");
        }
    }
}
</script
复制代码

注意: commit只能接受两个参数,如果你想要的传递多个参数时,请将载荷作为对象提交

store.commit 提交mutation的唯一方法,该方法至多接受两个参数:
    参数一 mutation type <String> 必传  需要触发的mutation函数名
    参数二 payload       <Any>     可选  提交给mutation的参数
复制代码
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
    state: {
        count1,
        arr: ['a''b''c'],
    },
    mutations: {
        addCurrentCount(state, num) {
            state.count += num
        },
        setArr(state, {index, value}) {
           Vue.set(state.arr, index , value)
        }
    }
})
复制代码
<template>
    <div>
        <button @click="setStateCount">add count</button>
        <button @click="setStateArr">setStateArr</button>
    </div>
</template>
<script>
export default {
    methods: {
        setStateCount() {
            this.$store.commit('addCurrentCount'7)
        },
        setStateArr() {
            // 此处载荷为对象
            this.$store.commit('setArr', {index:0, value: 7})
        }
    }
}
</script>
复制代码

注意: Mutation 需遵守 Vue 的响应规则 Vuex 的 store 中的状态是响应式的,那么当我们变更状态时,监视状态的 Vue 组件也会自动更新。这也意味着 Vuex 中的 mutation 也需要与使用 Vue 一样遵守一些注意事项:

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

2、当需要在对象上添加新属性时,你应该使用 Vue.set(obj, 'newProp', 123), 或者以新对象替换老对象

Vue.set(obj, 'newProp', 123)

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
    state: {
        count1,
        arr: ['a''b''c'],
        obj: {
            name'小华',
            age15
        }
    },
    mutations: {
        addCount(state) {
            state.count ++
        },
        setArr(state) {
            // vuex 依然遵循响应式原则,在vue的响应式原则中,通过下标修改添加数组的其中一项是不会引起页面更新的
            // state.arr[0] = 'A'
            // vuex没有this,所以只能先 import Vue,在使用Vue.set方法修改数组
            Vue.set(state.arr, 0 , 'A')
        },
        setObj(state) {
            // state.obj.address = 'hz'  // Error 直接给对象通过key添加属性也不会引起页面更新
            Vue.set(state.obj,'address''gz' )
        }
    }
})
复制代码

新对象替换老对象

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
    state: {
        arr: ['a''b''c'],
        obj: {
            name'小华',
            age15
        }
    },
    mutations: {
        addCount(state) {
            state.count ++
        },
        setArr(state) {
            // vuex 依然遵循响应式原则,在vue的响应式原则中,通过下标修改添加数组的其中一项是不会引起页面更新的
            // state.arr[0] = 'A'
            // 以新对象替换老对象方法引起引用数据类型响应变化
            state.arr = state.arr.map((item,index) => {
                if(index === 0) {
                    return 'AA'
                }
                return item
            })
        },
        setObj(state) {
            // state.obj.address = 'hz'  // Error 直接给对象通过key添加属性也不会引起页面更新
            state.obj = {
                ...state.obj,
                address'广州'
            }
        }
    }
})
复制代码

注意: Mutation 内部只允许同步操作,原因在vuex中mutation是唯一可以修改state的地方,所以开发中我们有时会对mutation进行数据追踪观察state是否按照预期发生改变,这样做的化就可以在开发中捕获大量的错误,如果mutation允许异步操作的话,就会导致我们的数据追踪变得混乱不可查阅。

注意:因为没次提交mutation都是穿入commit函数一个同名mutation字符串,这样的操作有时会导致提交字符串与mutation名不一致导致指定mutation没有被提交,这个问题尤其会出现在多人开发中。为了避免该问题vuex推荐专门创建一个名为 mutation-types.js 文件存储当前应用中所有的mutation名,开发人员就可以使用变量的方式声明提交指定的mutation了

语法

// 路径 /store/mutation-types.js
// mutation 名字都是用常量的的形式,一般规定常量使用全部大写的形式每个单词用下划线隔开
// 每个mutation名都需要打个注释解释下他是干嘛
export const ADD_CURRENT_COUNT = 'ADD_CURRENT_COUNT'

export const SET_ARR = 'SET_ARR'

export const SET_OBJ = 'SET_OBJ'
复制代码
// store中的mutation全部使用 mutation-types声明好的常量作为函数名
import Vue from 'vue'
import Vuex from 'vuex'
import * as types from './mutation-types'

Vue.use(Vuex)

export default new Vuex.Store({
    mutations: {
        // ES6 动态键名 将mutation-types中 声明好的常量作为当前mutation的函数名
        [types.ADD_CURRENT_COUNT](state, num) {
               // some code...
        },
        [types.SET_ARR](state, {index, value}) {
              // some code...
        },
        [types.SET_OBJ](state) {
            // some code...
        }
    }
})
复制代码
// 组件调用这些mutation时 直接通过引入对应的mutation-types常量
<script>
import {ADD_CURRENT_COUNT, SET_ARR, SET_OBJ} from '../store/mutation-types'

export default {
    methods: {
        setStateCount() {
            this.$store.commit(ADD_CURRENT_COUNT, 7)
        },
        setStateArr() {
            this.$store.commit(SET_ARR)
        },
        setStateObj() {
            this.$store.commit(SET_OBJ)
        }
    }
}
</script>
复制代码

2、Vuex为 mutations 同样提供了辅助函数 mapMutations

mapMutations生成的方法只接受一个参数,这个参数就是当前mutation的载荷。

方法一: mapMutations可以接收一个字符串数组作为参数,数组中的每一项字符串都会成为当前组件的方法并且与Vuex中的同名mutation建立映射对应关系。

<template>
    <div>
        // 提交载荷
        <button @click="ADD_CURRENT_COUNT(3)">add count</button>
        // 不提交载荷 
        <button @click="SET_ARR">set arr</button>
    </div>
</template>
<script>
import {mapMutations} from 'vuex'
import {ADD_CURRENT_COUNT, SET_ARR} from '../store/mutation-types'

export default {
    methods: {
        ...mapMutations([ ADD_CURRENT_COUNT, SET_ARR ]),
    }
}
</script>
复制代码

方法二: mapMutations可以接收对象作为参数,对象的每一个key都会成为当前组件的方法名,value必须是mutation的同名字符串与Vuex中的mutation建立映射对应关系。

<template>
    <div>
        // 提交载荷
        <button @click="addCount(3)">add count</button>
        // 不提交载荷 
        <button @click="setArr">set arr</button>
    </div>
</template>
<script>
import {mapMutations} from 'vuex'
import {ADD_CURRENT_COUNT, SET_ARR} from '../store/mutation-types'

export default {
    methods: {
        ...mapMutations({ 
         addCount: ADD_CURRENT_COUNT, 
         setArr: SET_ARR }),
    }
}
</script>
复制代码

Actions

概念Vuex给开发人员提供了一个可以执行异步操作的函数action

注意: action函数中接收两个参数

参数一 context 与 store对象相似所以可以访问 context.state / context.getters / context.commit / context.dispatch

参数二 action的载荷,action载荷与mutation一样只有一个如果需要传递多个参数请传递对象

★★★ action是不允许直接修改state的

语法在Vuex中通过配置选项actions创建,并使用 $store.dispatch方法分发action

1、在应用中通过store.dispatch('action名',载荷)的形式调用

import Vue from 'vue'
import Vuex from 'vuex'
import * as types from './mutation-types'

Vue.use(Vuex)

export default new Vuex.Store({
    state: {
        result: {}
    },
    mutations: {
        [types.SET_RESULT](state, result) {
            // 修改state
            state.result = result
        }
    },
    actions: {
        searchMusic(context, keywords) {
            console.log(context)
            fetch('http://musicapi.leanapp.cn/search?keywords=' + keywords)
            .then(res => res.json())
            // 只有mutation才能修改state,所以action异步请求数据后,只能通过提交mutation修改state
            .then(({result}) => context.commit(types.SET_RESULT, result))
        }
    }
})
复制代码
<script>
export default {
  created() {
      this.$store.dispatch('searchMusic', '海阔天空')
  }
};
</script>
复制代码

2、Vuex为 action 同样提供了辅助函数 mapActions

mapActions生成的方法只接受一个参数,这个参数就是当前action的载荷。

方法一: mapActions可以接收一个字符串数组作为参数,数组中的每一项字符串都会成为当前组件的方法并且与Vuex中的同名action建立映射对应关系。

<script>
import {mapActions} from 'vuex'

export default {
  created() {
    //   this.$store.dispatch('searchMusic','海阔天空')
    this.searchMusic('海阔天空')
  },
  methods: mapActions(['searchMusic']),
};
</script>
复制代码

方法二: mapAction可以接收对象作为参数,对象的每一个key都会成为当前组件的方法名,value必须是action的同名字符串与Vuex中的action建立映射对应关系。

<script>
import {mapState, mapActions} from 'vuex'

export default {
  created() {
    //   this.$store.dispatch('searchMusic','海阔天空')
    this.search('海阔天空')
  },
  methods: mapActions({search'searchMusic'}),
};
</script>
复制代码

Modules

由于篇幅较长,Vuex模块化的使用写在了下一篇文章中。

总结

  1. 应用层级的状态都应该集中在store中

  2. 提交 mutation 是更改状态state的唯一方式,并且这个过程是同步的。

  3. 异步的操作应该都放在action里面

系列链接

CodeSandbox在线代码演示

注意 如遇到CodeSandBox打开失败,请尝试按下图操作,然后分窗口就能一边看代码一边看效果啦 sandbox.png

文章分类
前端
文章标签