超详细!Vuex手把手教程

1,前言


最近在重温vue全家桶,再看一遍感觉记忆更深刻,所以专门记录一下(本文vuex版本为v3.x)。

2,Vuex 是什么


Vuex是专为Vue.js开发的状态管理模式。它采用集中式存储,管理所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化(我的理解就是全局变量)。

3,5大属性说明


state

对象类型,类似于实例的 data属性,存放数据

getters

对象类型,类似于实例的计算属性 computed

mutations

对象类型,类似于实例的 methods,但是不能处理异步方法

actions

对象类型,类似于实例的 methods,可以处理异步方法

modules

对象类型,当state内容比较多时,通过该属性分割成小模块,每个模块都拥有自己的 state、mutation、action、getter

4,state


存储在state中的数据和Vue实例中的data遵循相同的规则,必须是纯粹的对象。

4.1 直接访问

this.$store.state.xxx

4.1 使用mapState映射

<template>
    <div id="communication">
        <p>计数:{{ getCount }}</p>
        <p>学校:{{ getSchool('我是参数') }}</p>
    </div>
</template>

<script>
import { mapState } from 'vuex'

export default {
    name: 'Vuex',
    data() {
        return {
            date: 1998
        }
    },
    computed: {
        ...mapState({
            // mapState默认会把state当第一个参数传进来
            getCount: state => state.count,
            getSchool(state) {
                return (val) => {
                    return state.school + val + this.date
                }
            }
        })
    },
    mounted() {
        // 直接取值
        console.log(this.$store.state.count)
    }
}
</script>

5,getters


getter的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算,并且默认接受state作为其第一个参数,也可以接受其他getter作为第二个参数(如下例)

5.1 先在vuex中定义getters

export default new Vuex.Store({
    state: {
        count: 0,
        school: '清华大学'
    },
    getters: {
        // 返回处理后的state值
        getValue(state) {
            return state.count + '!'
        },
        // 返回调用自身getters处理后的state值
        getGetters(state, getters) {
            return state.school + getters.getValue
        },
        // 接受外部传参后处理的值(在通过方法访问时,每次都会去进行调用,而不会缓存结果)
        getParam(state) {
            return (param) => {
                return state.school + param
            }
        }
    },
    mutations: {},
    actions: {},
    modules: {}
})

5.2 直接获取值

// 取值
console.log(this.$store.getters.getGetters)
// 传参取值
console.log(this.$store.getters.getParam('param'))

5.3 使用mapGetters映射

<template>
    <div id="communication">
        <p>计数:{{ getGetters }}</p>
        <p>学校:{{ getParam(date) }}</p>
    </div>
</template>

<script>
import { mapGetters } from 'vuex'

export default {
    name: 'Vuex',
    data() {
        return {
            date: 1998
        }
    },
    computed: {
        ...mapGetters([
            'getGetters',
            'getParam'
        ])
    },
    mounted() {
        // 直接取值
        console.log(this.$store.getters.getGetters)
        console.log(this.getParam(this.date))
    }
}
</script>

6,Mutation


通过调用this.$store.commit('xxx'),调用mutation中的方法,更改store中的值

6.1,先在mutations中注册事件

export default new Vuex.Store({
    state: {
        count: 0,
        school: '清华大学'
    },
    getters: {},
    mutations: {
        // 默认state作为第一个参数
        handleAdd(state) {
            state.count++
        },
        // 接受传参
        handleChange(state, value) {
            state.school = value
        }
    },
    actions: {},
    modules: {}
})

6.2,在组件中调用方法commit修改值

<template>
    <div id="communication">
        <p>计数:{{ count }}</p>
        <el-button @click="handleStoreAdd">增加</el-button>
        <el-button @click="handleStoreChange">传参</el-button>
    </div>
</template>

<script>
import { mapState } from 'vuex'

export default {
    name: 'Vuex',
    data() {
        return {
            school: '武汉大学'
        }
    },
    computed: {
        ...mapState([
            'count'
        ])
    },
    methods: {
        // 调用修改
        handleStoreAdd() {
            this.$store.commit('handleAdd')
        },
        // 传递参数修改
        handleStoreChange() {
            this.$store.commit('handleChange', this.school)
        }
    }
}
</script>

6.3,使用常量定义方法名

新建文件mutation-types.js,定义方法名的常量,并导出

export const ADD_COUNT = 'ADD_COUNT'
export const CHANGE = 'CHANGE'

在store中

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

Vue.use(Vuex)

export default new Vuex.Store({
    state: {
        count: 0,
        school: '清华大学'
    },
    getters: {},
    mutations: {
        // 默认state作为第一个参数
        [MT.ADD_COUNT](state) {
            state.count++
        },
        // 接受传参
        [MT.CHANGE](state, value) {
            state.school = value
        }
    },
    actions: {},
    modules: {}
})

在组件中

<template>
    <div id="communication">
        <p>计数:{{ count }}</p>
        <el-button @click="handleStoreAdd">增加</el-button>
        <el-button @click="handleStoreChange">传参</el-button>
    </div>
</template>

<script>
import { mapState } from 'vuex'
import * as MT from '../../store/mutation-types'
export default {
    name: 'Vuex',
    data() {
        return {
            school: '武汉大学'
        }
    },
    computed: {
        ...mapState([
            'count'
        ])
    },
    methods: {
        // 调用修改
        handleStoreAdd() {
            this.$store.commit(MT.ADD_COUNT)
        },
        // 传递参数修改
        handleStoreChange() {
            this.$store.commit(MT.CHANGE, this.school)
        }
    }
}
</script>

6.4,使用mapMutations映射

<template>
    <div id="communication">
        <p>计数:{{ count }}</p>
        <p>计数:{{ school }}</p>
        <el-button @click="handleStoreAdd">增加</el-button>
        <el-button @click="handleStoreChange(schools)">传参</el-button>
    </div>
</template>

<script>
import { mapState, mapMutations } from 'vuex'
import * as MT from '../../store/mutation-types'

export default {
    name: 'Vuex',
    data() {
        return {
            schools: '武汉大学'
        }
    },
    computed: {
        ...mapState([
            'count',
            'school'
        ])
    },
    methods: {
        ...mapMutations({
            handleStoreAdd: MT.ADD_COUNT,
            handleStoreChange: MT.CHANGE
        })
    }
}
</script>

7,Action


注意,Action提交的是mutation,而不是直接变更状态,并且可以包含任意异步操作

7.1,在store中定义

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

Vue.use(Vuex)

export default new Vuex.Store({
    state: {
        count: 0,
        school: '清华大学'
    },
    getters: {},
    mutations: {
        // 默认state作为第一个参数
        [MT.ADD_COUNT](state) {
            state.count++
        },
        // 接受传参
        [MT.CHANGE](state, value) {
            state.school = value
        }
    },
    actions: {
        add(context) {
            context.commit(MT.ADD_COUNT)
        }
    },
    modules: {}
})

7.2,在组件中使用

<template>
    <div id="communication">
        <p>计数:{{ count }}</p>
        <el-button @click="actionAdd">增加</el-button>
    </div>
</template>

<script>
import { mapState, mapMutations } from 'vuex'
import * as MT from '../../store/mutation-types'

export default {
    name: 'Vuex',
    data() {
        return {
            schools: '武汉大学'
        }
    },
    computed: {
        ...mapState([
            'count',
            'school'
        ])
    },
    methods: {
        ...mapMutations({
            handleStoreAdd: MT.ADD_COUNT,
            handleStoreChange: MT.CHANGE
        }),
        // 调用action的方法,需要使用$store.dispatch
        actionAdd() {
            this.$store.dispatch('add')
        }
    }
}
</script>

7.3,使用mapActions映射

import { mapActions } from 'vuex'

methods: {
    ...mapActions([
        'moduleFn'
    ])
}

或者

import { mapActions } from 'vuex'

methods: {
    ...mapActions([
        fn: 'moduleFn'
    ])
}

7.4,简化写法

Action接受一个与store实例具有相同方法和属性的context参数对象,因此你可以调用context.commit提交一个mutation,或者通过context.statecontext.getters来获取stategetters,利用ES6的解构,可以简化写法。

actions: {
  add({ commit, state }) {
    commit(MT.CHANGE, state.school)
  }
}

7.5,执行异步操作

在vuex中

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

Vue.use(Vuex)

export default new Vuex.Store({
    state: {
        count: 0
    },
    getters: {},
    mutations: {
        // 默认state作为第一个参数
        [MT.ADD_COUNT](state) {
            state.count++
        }
    },
    actions: {
        add({ commit }) {
            return new Promise((resolve, reject) => {
                setTimeout(() => {
                    commit(MT.ADD_COUNT)
                    resolve()
                }, 1000)
            })
        }
    },
    modules: {}
})

在组件中使用async / await或者then / catch处理异步

<template>
    <div id="communication">
        <p>计数:{{ count }}</p>
        <el-button @click="actionAdd">增加</el-button>
    </div>
</template>

<script>
import { mapState, mapMutations } from 'vuex'
import * as MT from '../../store/mutation-types'

export default {
    name: 'Vuex',
    data() {
        return {
            schools: '武汉大学'
        }
    },
    computed: {
        ...mapState([
            'count',
            'school'
        ])
    },
    methods: {
        ...mapMutations({
            handleStoreAdd: MT.ADD_COUNT,
            handleStoreChange: MT.CHANGE
        }),
        // 调用action的方法,需要使用$store.dispatch
        async actionAdd() {
            await this.$store.dispatch('add')
            console.log(1998)
        }
    }
}
</script>

8,Modules


当应用变得非常复杂时,store对象就可能变得相当臃肿。这时候可以将store分割成模块,每个模块拥有自己的statemutationactiongetter、甚至是嵌套子模块,从上至下进行同样方式的分割。

8.1,准备工作

在store目录下新建Modules文件夹,在Modules文件夹中新建modulesA.jsmodulesB.js,如下图

目录.png

在modulesA.js中写上局部模块的statemutationactiongetter,并导出

const moduleA = {
    state: () => ({
        a: '我是moduleA'
    }),
    getters: {},
    mutations: {},
    actions: {}
}

export default moduleA

然后在storeindex.js中引入,并丢进modules对象里

import Vue from 'vue'
import Vuex from 'vuex'
import * as MT from './mutation-types'
import moduleA from './modules/moduleA'
import moduleB from './modules/moduleB'

Vue.use(Vuex)

export default new Vuex.Store({
    state: {
        count: 0
    },
    getters: {},
    mutations: {},
    actions: {},
    modules: {
        moduleA,
        moduleB
    }
})

8.2,使用modules中注入的模块的state

在组件中直接使用

this.$store.state.moduleA.xxx

在组件中使用mapState映射

<span>{{ moduleA.xxx }}</span>

import { mapState } from 'vuex'

computed: {
    ...mapState([
        'moduleA'
    ])
}

8.3,使用modules中注入模块的getters

在组件中直接使用

this.$store.getters.getModuleA

在组件中使用mapState映射

<p>{{ getModuleA }}</p>

import { mapGetters } from 'vuex'

computed: {
    ...mapGetters([
        'getModuleA'
    ])
}

模块内部的getter,接受的参数stategetters是模块的局部状态对象,而根节点的状态会作为第三个参数rootState暴露出来

const moduleA = {
    getters: {
        getModuleA(state, getters, rootState) {
            return state.xxx + '---' + rootState.xxx
        }
    }
}

如果需要带参数

const moduleA = {
    getters: {
        getModuleA(state, getters, rootState) {
            return (value) => {
                return state.a + '---' + value
            }
        }
    }
}

8.4,使用modules中注入模块的mutations

在组件中直接使用

this.$store.commit('setModuleA') || this.$store.commit('setModuleA', '参数')

在组件中使用mapMutations映射

import { mapMutations } from 'vuex'

methods: {
    ...mapMutations([
        openFn: 'setModuleA'
    ])
}

模块内部的mutations,默认接受的第一个参数state是模块的局部状态对象

const moduleA = {
    mutations: {
        setModuleA(state) {
            state.xxx += 'xxx'
        }
    }
}

如果需要带参数

const moduleA = {
    mutations: {
        setModuleA(state, value) {
            state.xxx += value
        }
    }
}

8.5,使用modules中注入模块的actions

在组件中直接使用

this.$store.dispatch('xxx')

在组件中使用mapActions映射

import { mapActions } from 'vuex'

methods: {
    ...mapActions([
        'moduleA'
    ])
}

或者重命名

import { mapActions } from 'vuex'

methods: {
    ...mapActions({
        fn: 'moduleA'
    })
}

对于模块内部的action,局部状态通过context.state暴露出来,根节点状态则为context.rootState

const moduleA = {
  // ...
  actions: {
    fn ({ state, commit, rootState }) {
      if ((state.count + rootState.count) % 2 === 1) {
        commit('increment')
      }
    }
  }
}

8.6,命名空间

默认情况下,模块内部的actionmutationgetter是注册在全局命名空间的,这样使得多个模块能够对同一mutationaction作出响应。如果希望模块具有更高的封装度和复用性,可以通过给模块添加namespaced: true的方式使其成为带命名空间的模块。当模块被注册后,它的所有getteractionmutation都会自动根据模块注册的路径调整命名。

8.6.1,使用

先在模块moduleB.js中添加namespaced: true

const moduleB = {
    namespaced: true,
    state: () => ({
        b: '我是moduleB'
    }),
    mutations: {},
    actions: {},
    getters: {}
}

export default moduleB

storeindex.js

import moduleA from './modules/moduleA'
import moduleB from './modules/moduleB'

export default new Vuex.Store({
    state: {},
    getters: {},
    mutations: {},
    actions: {},
    modules: {
        moduleA,
        moduleB
    }
})

如果在组件中使用命名空间,需要带上空间名称,mapState, mapGetters, mapMutationsmapActions用法一样。

<script>
import { mapState, mapGetters, mapMutations } from 'vuex'

export default {
    name: 'Vuex',
    data() {
        return {}
    },
    computed: {
        // 此处注入的是moduleA模块的数据
        ...mapState('moduleA', [
            'a'
        ]),
        // 需要注入moduleB模块,就再写一个
        ...mapState('moduleB', [
            'b'
        ])
    },
    mounted() {
        // 直接使用
        console.log(this.$store.state.moduleA.a)
        console.log(this.$store.state.moduleB.b)
    },
    methods: {}
}
</script>

8.6.2 ,在带命名空间的模块中内访问全局内容

如果你希望使用全局的stategetterrootStaterootGetters会作为第三和第四参数传入getter,也会通过context对象的属性传入action。若需要在全局命名空间内分发action或提交mutation,将{ root: true }作为第三参数传给dispatchcommit即可

const moduleA = {
    namespaced: true,
    state: () => ({
        a: '我是moduleA'
    }),
    getters: {
        getModuleA(state, getters, rootState, rootGetters) {
            // 使用全局命名空间的state或getters
            return state.a + rootState.count
        }
    },
    mutations: {
        setModuleA(state) {
            console.log(state.a)
        }
    },
    actions: {
        addM({ state, commit, dispatch, rootState, rootGetters }) {
            console.log(rootState)
            console.log(rootGetters)
            // 调用全局命名空间的方法
            dispatch('rootFunction', null, { root: true })
        }
    }
}

export default moduleA

8.6.3,在带命名空间的模块注册全局action

在带命名空间的模块注册全局action,需要添加root: true,并将这个action的定义放在函数handler中,其中,handler的第一个参数namespacedContext就是action中的Context参数

const moduleA = {
    namespaced: true,
    state: () => ({
        a: '我是moduleA'
    }),
    getters: {},
    mutations: {},
    actions: {
        rootFn: {
            root: true,
            handler(namespacedContext, param) {
                console.log(namespacedContext.state)
            }
        }
    }
}

export default moduleA

本次分享就到这儿啦,我是@鹏多多,如果您看了觉得有帮助,欢迎评论,关注,点赞,转发,我们下次见~

往期文章

个人主页

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容