Vue学习笔记(十一) Vuex

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>

5、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>

【 阅读更多 Vue 系列文章,请看 Vue学习笔记

posted @ 2019-07-26 23:21  半虹  阅读(216)  评论(0编辑  收藏  举报