1、介绍
Vuex 是一个为 Vue 应用程序开发的状态管理模式,它用集中式存储来管理应用所有组件的状态
简单来说,它的作用就是把所有组件的共享状态抽取出来,以一个全局单例的模式进行管理
我们可以把 Vuex 理解成一个 store,里面存储着所有组件共享的 state(数据)和 mutations(操作)
这里还是先附上官方文档的链接:https://vuex.vuejs.org/zh/,有兴趣的朋友可以去看看
2、安装
(1)通过 CDN 引用
<script src="https://unpkg.com/vue"></script> <script src="https://unpkg.com/vuex"></script>
(2)通过 NPM 安装与使用
- 安装
> npm install vuex
- 使用
在项目中需要通过 Vue.use()
明确安装 Vuex
import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex)
3、State
Vuex 中的 state 用于集中存储数据,当我们需要访问 state 时,可以先将其映射为计算属性
由于 state 是响应式的,所以当 state 发生变化时,它会重新求取计算属性,并自动更新相应的 DOM
<!DOCTYPE html> <html> <head> <title>Demo</title> <script src="https://unpkg.com/vue"></script> <script src="https://unpkg.com/vuex"></script> </head> <body> <div id="app"> <my-counter></my-counter> </div> <script> // 1、定义 store const store = new Vuex.Store({ state: { // 定义 state count: 0 } }) // 2、定义组件 const Counter = { template: ` <div> <p>{{ count }}</p> </div> `, // 如果需要访问 state,可以在计算属性中通过 this.$store.state 返回数据 computed: { count() { return this.$store.state.count } } } // 3、创建并挂载根实例 const app = new Vue({ store, // 注入 store components: { 'my-counter': Counter } }).$mount('#app') </script> </body> </html>
假设我们要在一个组件中使用多个 state,如果为每个状态都写一条语句将其映射为计算属性未免太过繁琐
所以 Vuex 提供 mapState()
辅助函数能够帮助我们完成这些工作
<!DOCTYPE html> <html> <head> <title>Demo</title> <script src="https://unpkg.com/vue"></script> <script src="https://unpkg.com/vuex"></script> </head> <body> <div id="app"> <my-counter></my-counter> </div> <script> const store = new Vuex.Store({ state: { counter1: 0, counter2: 10, counter3: 100 } }) const Counter = { template: ` <div> <p>couter1: {{ counter1 }}</p> <p>couter2: {{ counter2 }}</p> <p>couter3: {{ counter3 }}</p> </div> `, data: function () { return { localCounter: 1 } }, computed: { // mapState() 返回一个对象,使用对象展开运算符将其混入 computed 对象 ...Vuex.mapState({ // 使用箭头函数,可以简化代码 counter1: state => state.counter1, // 使用字符串 'counter2',等价于 `state => state.counter2` counter2: 'counter2', // 使用常规函数,可使用 `this` 以获取局部状态 counter3 (state) { return state.counter3 + this.localCounter } }) } } const app = new Vue({ store, components: { 'my-counter': Counter } }).$mount('#app') </script> </body> </html>
4、Getter
Vuex 中的 getter 用于管理派生出来的状态
它就相当于计算属性一样,会被缓存起来,当依赖发生改变时才会重新计算
<!DOCTYPE html> <html> <head> <title>Demo</title> <script src="https://unpkg.com/vue"></script> <script src="https://unpkg.com/vuex"></script> </head> <body> <div id="app"> <my-todo></my-todo> </div> <script> const store = new Vuex.Store({ state: { todos: [ { id: 1, text: 'Say Hello', done: true}, { id: 2, text: 'Say Goodbye', done: false} ] }, getters: { // 定义 getters // 其接受 state 作为第一个参数 doneTodos: state => { return state.todos.filter(todo => todo.done) }, // 其接受 getters 作为第二个参数 doneTodosCount: (state, getters) => { return getters.doneTodos.length } } }) const Todo = { template: ` <div> <p>{{ doneTodosCount }} task(s) done</p> <ul><li v-for="item in doneTodos">{{ item.text }}</li></ul> </div> `, computed: { doneTodos () { return this.$store.getters.doneTodos }, doneTodosCount () { return this.$store.getters.doneTodosCount } } } const app = new Vue({ store, components: { 'my-todo': Todo } }).$mount('#app') </script> </body> </html>
和 state 一样,getters 也有一个名为 mapGetters()
的辅助函数将其映射为计算属性
computed: { ...mapGetters([ 'doneTodos', 'doneTodosCount' ]) }
如果要给 getter 重命名,可以用对象形式
computed: { ...mapGetters({ doneTodosAlias: 'doneTodos', doneTodosCountAlias: 'doneTodosCount' }) }
5、Mutation
上面我们讲了怎么访问 state,下面我们来看看怎么修改 state,改变状态的唯一方法是提交 mutation
这里,请记住一条重要的规则:mutation 必须是同步函数
<!DOCTYPE html> <html> <head> <title>Demo</title> <script src="https://unpkg.com/vue"></script> <script src="https://unpkg.com/vuex"></script> </head> <body> <div id="app"> <my-counter></my-counter> </div> <script> const store = new Vuex.Store({ state: { count: 0 }, mutations: { // 定义 mutations // 传入的第一个参数是 state increment(state) { state.count += 1 }, // 传入的第二个参数是 payload,可以提供额外的信息 incrementN(state, payload) { state.count += payload.amount } } }) const Counter = { template: ` <div> <p>{{ count }}</p> <button @click="increment"> 加 1 </button> <button @click="incrementN"> 加 10 </button> </div> `, // 如果需要访问 state,可以在计算属性中通过 store.state 返回数据 computed: { count() { return store.state.count } }, // 如果需要修改 state,可以通过 store.commit() 提交 mutation methods: { increment() { store.commit('increment') }, incrementN() { // 以对象的形式给 commit() 传入 payload store.commit('incrementN', { amount: 10 }) } } } const app = new Vue({ store, components: { 'my-counter': Counter } }).$mount('#app') </script> </body> </html>
当然,我们也可以使用 mapMutations()
辅助函数将 mutation 映射为 methods
methods: { ...mapMutations([ 'increment', 'incrementN' ]) }
也同样可以使用对象形式支持重命名
methods: { ...mapMutations({ add: 'increment', addN: 'incrementN' }) }
6、Action
还记得上面我们说过 mutation 只能是同步函数,若需要使用异步操作,则可以通过分发 action
action 内部可以包含异步逻辑,它做的工作是提交 mutation,而不是直接改变状态
<!DOCTYPE html> <html> <head> <title>Demo</title> <script src="https://unpkg.com/vue"></script> <script src="https://unpkg.com/vuex"></script> </head> <body> <div id="app"> <my-counter></my-counter> </div> <script> const store = new Vuex.Store({ state: { count: 0 }, mutations: { increment(state) { state.count += 1 }, incrementN(state, payload) { state.count += payload.amount } }, actions: { //定义 actions // 该方法接受一个与 store 实例具有相同属性和方法的对象作为参数 // 我们可以调用 context.commit() 提交 mutation // 也可以通过 context.state 和 context.getters 访问 state 和 getters incrementAsync(context) { setTimeout(() => { context.commit('increment') }, 1000) }, // 和 mutations 一样,也可以传入第二个参数 payload incrementNAsync(context, payload) { setTimeout(() => { context.commit('incrementN', payload) }, 1000) } } }) const Counter = { template: ` <div> <p>{{ count }}</p> <button @click="increment"> 同步加 1 </button> <button @click="incrementN"> 同步加 10 </button> <button @click="incrementAsync"> 异步加 1 </button> <button @click="incrementNAsync"> 异步加 10 </button> </div> `, computed: { count() { return store.state.count } }, methods: { // 通过 store.commit() 提交 mutation increment() { store.commit('increment') }, incrementN() { // 以对象的形式给 commit() 传入 payload store.commit('incrementN', { amount: 10 }) }, // 通过 store.dispatch() 分发 action incrementAsync() { store.dispatch('incrementAsync') }, incrementNAsync() { // 以对象的形式给 dispatch() 传入 payload store.dispatch('incrementNAsync', { amount: 10 }) } } } const app = new Vue({ store, components: { 'my-counter': Counter } }).$mount('#app') </script> </body> </html>
如果需要处理更复杂的异步逻辑,我们也可以使用 Promise 和async/await
<!DOCTYPE html> <html> <head> <title>Demo</title> <script src="https://unpkg.com/vue"></script> <script src="https://unpkg.com/vuex"></script> </head> <body> <div id="app"> <my-counter></my-counter> </div> <script> const store = new Vuex.Store({ state: { count: 0 }, mutations: { add(state) { state.count += 10 }, multiply(state) { state.count *= 10 } }, actions: { addAsync(context) { setTimeout(() => { context.commit('add') }, 1000) }, multiplyAsync(context) { setTimeout(() => { context.commit('multiply') }, 1000) }, // 只允许使用异步函数 addAsync 和 multiplyAsync,实现先乘后加 // 使用 async/await async multiplyBeforeAdd(context) { await context.dispatch('multiplyAsync') context.dispatch('addAsync') }, // 只允许使用异步函数 addAsync 和 multiplyAsync,实现先加后乘 // 使用 async/await async addBeforeMultiply(context) { await context.dispatch('addAsync') context.dispatch('multiplyAsync') } } }) const Counter = { template: ` <div> <p>{{ count }}</p> <button @click="done"> 乘10,加10,加10,乘10 </button> </div> `, computed: { count() { return store.state.count } }, methods: { // 先完成先乘后加,再完成先加后乘 done() { store.dispatch('multiplyBeforeAdd').then(() => { store.dispatch('addBeforeMultiply') }) } } } const app = new Vue({ store, components: { 'my-counter': Counter } }).$mount('#app') </script> </body> </html>
文章知识点与官方知识档案匹配,可进一步学习相关知识