vuex摸鱼笔记

284 阅读7分钟

c037f0436be40be480f183f2af205980.jpeg

最近一个月发版好几次,都没时间摸鱼了,所以乘着发版空挡期,整理些常用的基础知识,方便更好的ctrl c v,增加摸鱼时间。(⊙o⊙) 开卷吧~~~

一、vuex 的 4 个模块

一般多个视图依赖于同一个状态数据, 或者层级比较深时, 可以考虑用 vuex, 基本使用如下:

  • 安装 vuex 依赖包:
npm install vuex --save
  • 导入 vuex 包
// store/index.js

import Vuex from 'vuex'
vue.use(Vuex)
  • 创建 store 对象
// store/index.js

...
export default new Vuex.Store({
    state,
    mutations,
    actions,
    getters
})

// 如果是需要每次新生成一个store, 可以写成function的形式
export default () => {
    return new Vuex.Store({
        state,
        mutations,
        actions,
        getters
    })
}
  • 将 store 对象挂载到 vue 实例中
// main.js
import store from '@/store/index'

new vue({
    el: 'app',
    router,
    store, //将创建的共享数据对象,挂载到vue实例中,所有的组件,就可以直接从store中获取全局的数据了
    render: (h) => h(app),
})

接下来看下vuex的4个模块的使用,附上官方的vuex流程图: ac21ae44a8bb2340ff0f8abf3b387583.jpeg

1.state

它是 vuex 管理的状态对象(唯一的), 类似页面的data。

// store/index.js

import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)

// 包含n个状态数据的对象, 相当于data
const state = {
    count: 0, // 初始值
    name: '',
    token: '',
}

页面使用 state 数据:

// xxx.vue

<template>
    <div>{{count}}</div>
</template>
<script>
export default {
    computed:{
        count(){
            return this.$store.state.count
        }
    }
}
</script>
<style>
</style>
2.mutations

包含多个用于直接更新状态数据(state)的方法的对象, 不能包含异步代码。另外 mutation 只能有连个参数, 第一个是 state, 第二个是传入的数据, 如果有多个参数需要传递, 需要把参数封装成对象传递(subscribe通过mutation.payload获取传入mutation的第二个参数)。

// store/index.js

const mutations = {
    // +data
    INCREMENT(state, data) {
        state.count = state.count + data
    },
    // -1
    DECREMENT(state) {
        state.count--
    },
}

页面通过 this.$store.commit('mutation 名称')触发 mutation, 或者通过 action 中的 commit('mutation 名称')触发。

// xxx.vue

...
mounted() {
    this.$store.commit('INCREMENT', 1);
}

// 虽然可以直接在页面通过this.$store.state.count = val,直接修改state中的数据,但是vue不推荐这么做,修改state里面的数据还是需要通过commit触发。
// 设置vuex的strict:true可以限制不能通过this.$store.state.count = val直接修改state, 一般在开发环境加入改限制。
const isDev = process.env.NODE_ENV === "development";
export default new Vuex.Store({
    strict: isDev ? true : false,
    state,
    mutations,
    actions,
    getters
})
...
3.actions

包含多个用于间接更新状态数据的方法的对象, 通过执行 commit('mutation 名称')来触发 mutation 的调用, 从而更新状态数据 state,。

// store/index.js

const actions = {
    decrement({ commit }) {
        commit('DECREMENT')
    },
    updateCount({ commit, state }, data) {
        if (state.count === 10) {
            commit('INCREMENT', data)
        }
    },
    updateCountAsync({ commit }, data) {
        setTimeout(() => {
            commit('INCREMENT', data)
        }, 1000)
    },
}

通过组件中的 this.$store.dispatch('mutation 名称', newData)触发

// xxx.vue

...
methods:{
    decrement(){
        this.$store.dispatch('decrement')
    },
    updateCount(){
        this.$store.dispatch('updateCount', 1)
    },
    incrementAsync(){
        this.$store.dispatch('incrementAsync', 1)
    },
}
...
4.getters

包含多个计算属性 getter 方法的对象,类似computed。

// store/index.js

const getters = {
    comName(state) {
        return `${state.name}:${state.count}`
    },
}

页面中通过 this.$store.getters.xxx 获取 getter 值。

// xxx.vue

...
computed: {
    comName () {
        return this.$store.getters.comName
    }
},
...

二、mapState、mapGatter、mapMutation 和 mapAction

使用mapState、mapGatter、mapMutation 和 mapAction可以简化上面的写法:

// xxx.vue

import { mapState, mapGetters, mapMutation, mapAction } from 'vuex'
computed:{
    ...mapState(['count']),
    ...mapGetters(['comName'])

    /*相当于
    count(){
        return this.$store.state.count
    },
    comName(){
        return this.$store.getters.comName
    }
    
    如果数据名字不一致,参数需要用对象表示
    ...mapState({
        count2: 'count'
        // 或者用方法形式
        // count2: (state) => state.count
    })*/
}
methods: {
    ...mapMutation(['INCREMENT']),
    // 也可以写成对象的形式
    // ...mapMutations({ increment: 'INCREMENT' })

    ...mapAction(['decrement'])
}
mounted() {
    this['INCREMENT'](1);
    // 相当于
    // this.$store.commit('INCREMENT', 1);

    this.decrement()
     // 相当于
    // this.$store.dispatch('decrement')
}

三、vuex的module模块

当模块比较多,比较复杂可以考虑使用vuex的module模块。默认情况下vuex会把数据和方法放在全局命名空间,如果不加模块名多个模块包含相同的方法名,调用的时候就会访问所有模块中的属性(即如果没有加namesapced:true, 页面是可以直接通过...mapAction(['setBreadcrumbList']); this.setBreadcrumbList(1);等触发模块的方法。),如果多个模块含有相同名称, 就会有问题。所以一般我们会开启模块命名空间:namesapced:true。当模块被注册后,它的所有getter、action及mutation都会自动根据模块注册的路径调整命名,也就是说,我们在调用这些方法时,需要加上这个文件的路径(比如我要访问pageset这个文件中的state里边的某个属性:this.$store.state.pageset。后边这个pageset就是多了个pageset.js模块名),相当于独立的区块去使用,模块和模块之间互不干扰。

// 1.store/index.js
import pageset from './pageset'
export default new Vuex.Store({
    state,
    mutations,
    actions,
    getters,
    // 模块化部分
    modules: {
        pageset,
    },
})

// 2.store/pageset/index.js
import state from './state'
import mutations from './mutations'
import actions from './actions'
import getters from './getters'
export default {
    namespaced: true, //开启命名空间,保证模块跟模块是独立的,这样不同的模块可以有相同的mutations或者actions方法名。
    state,
    getters,
    mutations,
    actions,
}

// 3.store/pageset/state.js
export default {
    userDetailInfo: {}, //用户详细信息
    breadcrumbList: [], // 面包屑导航列表
}

// 4.store/pageset/mutations.js
export const SET_USER_DETAIL_INFO = 'SET_USER_DETAIL_INFO' //设置用户详细信息
export const SET_BREADCRUMB_LIST = 'SET_BREADCRUMB_LIST' // 设置面包屑导航列表
export const CLEAR_BREADCRUMB_LIST = 'CLEAR_BREADCRUMB_LIST' // 清空面包屑导航列表

// 5.store/pageset/mutations.js
import * as types from './mutation-types'
export default {
    [types.SET_USER_DETAIL_INFO](state, data) {
        // state为pageset模块内部的state
        state.userDetailInfo = data
    },
    [types.SET_USER_DETAIL_INFO](state, { key, value }) {
        state.userDetailInfo[key] = value
    },
    [types.SET_BREADCRUMB_LIST](state, list) {
        state.breadcrumbList = [...list]
    },
    [types.CLEAR_BREADCRUMB_LIST](state) {
        state.breadcrumbList = []
    },
}

// 6.store/pageset/actions.js
import * as types from './mutation-types'
export default {
    setUserDetailInfo({ commit }, params) {
        commit(types.SET_USER_DETAIL_INFO, params)
    },
    setUserInfoByKey({ commit }, params) {
        commit(types.SET_USER_DETAIL_INFO, params)
    },
    setBreadcrumbList({ commit }, params) {
        commit(types.SET_BREADCRUMB_LIST, params)
    },
    clearBreadcrumbList({ commit }) {
        commit(types.CLEAR_BREADCRUMB_LIST)
    },
}

// 7.store/pageset/getters.js
export default {
    userInfo: (state) => state.userDetailInfo,
}

页面获取/修改数据:

mounted() {
    // 获取面包屑导航数据state
    console.log(this.$store.state.pageset.breadcrumbList)
    //获取getters数据
    console.log(this.$store.getters['pageset/userInfo'])

    // 触发mutataion
    this.$store.commit('pageset/SET_BREADCRUMB_LIST', list)
    // 清空数据action
    this.$store.dispatch('pageset/clearBreadcrumbList');
    // 设置
    this.$store.dispatch('pageset/setBreadcrumbList', list);
},

使用 mapState、mapGatter、mapMutation 和 mapAction 的写法:

// xxx.vue
import {mapState, mapGetters, mapMutations, mapActions} from 'vuex'

computed: {
    ...mapState({
        breadcrumbList: state => state.pageset.breadcrumbList
    })
    
    ...mapGetters({
        userInfo: getter => getter.pageset.userInfo
        // userInfo: 'pageset/userInfo'
    })

    // 或者用数组写法(简写)
    // ...mapState('pageset', ['userDetailInfo','breadcrumbList'])
    // ...mapGetters('pageset', ['userInfo'])
    
    // 或者
    // ...mapGetters(['pageset/userInfo'])

},

methods:{
	...mapMutaions('pageset', ['SET_BREADCRUMB_LIST']),
    // 或者
    // ...mapMutations(['pageset/SET_BREADCRUMB_LIST']),

    ...mapActions('pageset', ['setBreadcrumbList', 'clearBreadcrumbList']),
}

mounted() {
    // 触发mutataion
    this[SET_BREADCRUMB_LIST](list);
    // 或者
    // this['pageset/SET_BREADCRUMB_LIST']();

    // 清空数据action
    this.clearBreadcrumbList();
    // 设置
    this.setBreadcrumbList(list);
},

但是如果数据比较多,或者这个模块下命名空间比较深入,还有其他模块,嵌套层级比较深就会变成下面这样子:

...mapState({
    breadcrumbList: state => state.pageset.xxx.breadcrumbList,
    a: state => state.pageset.xxx.a,
    b: state => state.pageset.xxx.b,
    c: state => state.pageset.xxx.c,
    d: state => state.pageset.xxx.d,
})
methods: {
    ...mapMutations(['pageset/xxx/SET_BREADCRUMB_LIST']),
    setInfo () {
        this['pageset/xxx/SET_BREADCRUMB_LIST'] ();
    }
}

会有很多重复部分,我们可以在 vue 文件中引入 createNamespacedHelpers,然后将模块名 pageset 作为参数,传进来。它会创建基于某个命名空间(pageset)辅助函数,并返回一个对象,对象里有新的绑定在 pageset 上的辅助函数。

// 引入createNamespacedHelpers
import { createNamespacedHelpers } from 'vuex';
// 定义mapState、mapMutations,并将模块名pageset传入createNamespacedHelpers
const { mapState, mapMutations } = createNamespacedHelpers('pageset');

computed: {
    ...mapState({
    breadcrumbList: state => state.breadcrumbLista: state => state.a,
    })
},
methods: {
    ...mapMutations(['SET_BREADCRUMB_LIST']),
},

以上触发的是pageset模块自身的mutations和action来修改模块自身的state,当然vuex也可以在模块内部获取全局的state,触发全局的mutations和action,例如下面在a 模块调用全局 store:

// store/a/getters.js
import * as types from './mutation-types'
export default {
    count2 (state, getters, roortState) {
        // state是当前a模块的state, rootState是全局命名空间的state。
        return state.
    }
}

// store/a/action.js
export default {
    getXXX({commit}, data) {
        // 触发全局的mutation, 传入全局的state
        commit('INCREMENT', rootState.count, { root: true });
    },
    getYYY(ctx, data) {
        let {commit, dispatch, state, rootState, rootGetters} = ctx;
        // 触发全局的action, 传入全局的state
        dispatch('updateCount', rootState.count, { root: true });
    }
}

同样多个模块之间也可以互相调用。例如b 模块调用 a 模块的mutations或者action,需要给mutations/action传递第三个参数{root: true}。action函数中第一个参数是context,其包含:{commit, dispatch, state, roootState, rootGetters}, 而commit/dispatch又包含三个参数:{mutationName, data, {root: true}},第一个参数是异步提交的mutation名,第二个参数是传进来的参数,第三个是配置选项,声明不是执行当前模块。

// store/a/action.js

import * as types from './mutation-types'
export default {
    getApp({commit}, data) {
        // 触发a模块的mutation
        commit('a/commitB', data, { root: true });
    },
    setApp({commit, dispatch, state, rootState, rootGetters}, data) {
        // 触发a模块的action
        dispatch('a/actionB', data, { root: true });
        // 使用a模块的state数据
        console.log(rootState.a.stateData);
        // 使用a模块的getter数据
        console.log(rootGetters['a/xxx'])
    }
}

四、vuex的动态加载模块

可以通过store.registerModule来实现动态加载c模块:

import store from '@/store/index'

store.registerModule('c', {
    state: {
        count3: 3
    },
    mutation,
    action,
    getters
})

// 引入
computed: {
    ...mapState({
        count3: state => state.c.count3
    })
},

// 可以通过unregisterModule解绑
// store.unregisterModule('c');

五、持久化数据

vuex能很好的实现多个视图间数据共享,但是如果刷新页面,vuex中的state数据会重新初始化,有些场景我们希望刷新页面数据保持不变(比如面包屑导航),可以使用localStorage存储数据(但是localStorage存储需要写很多setItem、getItem),也可以使用vuex结合vuex-persistedstate插件实现数据持久化,下面来看下vuex-persistedstate是如何使用的:

首先安装插件:

npm install vuex-persistedstate --save

使用vuex-persistedstate默认会将数据存储到localStorage:

import Vue from 'vue'
import Vuex from 'vuex'
import persistedState from 'vuex-persistedstate'

Vue.use(Vuex)
const state = {
    activeIndex: "0", // 当前选中tab
    activeMenuIndex: 0, // 当前选菜单下标
    activeMenuSubIndex: 0, // 当前选子菜单下标
}
...

export default new Vuex.Store({
    modules: {
        pageset,
    },
    state,
    mutations,
    actions,
    getters,
    plugins: [persistedState()]
})

当然可以修改storage配置将数据存储到sessionStorage:

import persistedState from "vuex-persistedstate"

export default newVuex.Store({
    modules: {
        pageset,
    },
    state,
    mutations,
    actions,
    getters,
    plugins: [
        persistedState({
            storage: window.sessionStorage
        })
    ]
})

默认情况下所有的数据都会持久化保存,可以通过修改配置选择需要持久化的数据:

import persistedState from "vuex-persistedstate"
 
export default newVuex.Store({
    modules: {
        pageset,
    },
    state,
    mutations,
    actions,
    getters,
    plugins: [
        persistedState({
            storage: window.localStorage,
            reducer(val)  {
                return {
                    // 只储存state中的token
                    assessmentData: val.token
                }
            }
        })
    ]
})

// 也可以选择某个vuex模块进行持久化存储
const store = new Vuex.Store({
    modules: {
        pageset,
        modules2,
    },
    state,
    mutations,
    actions,
    getters,
    plugins: [
        persistedState({
            key: "per-vuex", // 浏览器中的名字
            paths: ["pageset", "modules2"] // 需要存储起来的参数模块
        })
    ]
});

六、vuex 其他配置

1.当使用commit或者dispatch更新vuex中的数据时, 模板渲染到界面的数据也会发生变化, 并且会刷新整个界面, 这时可以使用vuex热更替功能:

// store/index.js
import { createStore } from 'vuex'
import state from './state'
import mutations from './mutations'
import actions from './actions'
import getters from './getters'
import pageset from './pageset/index'

...
const store = createStore({
    state,
    mutations,
    actions,
    getters,
    modules: {
        pageset: pageset
    }
})

if(module.hot) {
    // accept传入的数组里面是上面引入文件的路径
    module.hot.accept(
        [
            './state', 
            './mutations',
            './actions',
            './getters',
            './pageset/index'
        ], () => {
            // 获取更新后的模块
            // 因为 babel 6 的模块编译格式问题,这里需要加上 `.default`
            const newState = require('./state').default;
            const newMutations = require('./mutations').default;
            const newActions = require('./actions').default;
            const newGetters = require('./getters').default;
            const newPageset = require('./pageset/index').default;
            
             // 加载新模块
            store.hotUpdate({
                state: newState,
                mutations: newMutations,
                actions: newActions,
                getters: newGetters,
                modules: {
                    pageset: pageset
                }
            })
          
        }
    )
}

export default store;

2.store的watch监听: 不同于页面的watch监听, 这里的watch需要传入两个函数, 第一个函数相当于一个getter函数, 当第一个函数的返回值发生变化的时候会触发第二个函数调用。

import store from '@/store/index'

store.watch((state) => state.count+1, (newCount) => {
    // 当 (state) => state.count+1的返回值有变化的时候, 会执行这里的逻辑
    console.log('新的值:', newCount);
})

3.store的subscribe订阅: subscribe订阅会拿到所有mutation的变化,每次有一个mutation被调用,都会执行subscribe回调函数。

// store/index.js

export default newVuex.Store({
    modules: {
        pageset,
    },
    state,
    mutations,
    actions,
    getters,
    plugins: [
       (store) =>{
           // vuex初始化的时候会调用plugins
           store.subscribe(() => {
               // mutation.type是当前被调用的mutation名称,mutation.payload是当前被调用的mutation的参数。
                store.subscribe((mutation, state) => {
                     console.log(mutation.type);
                     console.log(mutation.payload);
                })
           })
       }
    ]
})

同理也可以通过subscribeAction订阅来监听到所有action的调用:

import store from '@/store/index'

// action.type是当前被调用的action名称,action.payload是当前被调用的mutation的参数。
store.subscribeAction((action, state) => {
     console.log(action.type);
     console.log(action.payload);
})

其他使用见官方文档: vuex.vuejs.org/zh/guide/ho…