最全的 vuex 详解使用

最全的 vuex 详解使用

前言

根据日常开发,整理了关于vuex的使用文档,附带一些网上主流的使用方式,适合刚入门的小白。

废话不多说,先上代码。

vuex 基础及 modules 模块化使用

目录结构:

store/
|- modules
    |- moduleA.js
    |- moduleB.js
|- action.js
|- getters.js
|- index.js
|- mutations-types.js
|- mutations.js
|- state.js

main.js

在 main 中引入使用 store

import store from './store/index.js';

Vue.prototype.$store = store;

const app = new Vue({
  store,
  render: (h) => h(App),
});

app.$mount('#app');

index.js

import Vue from 'vue';
import Vuex from 'vuex';
import state from './state';
import mutations from './mutations';
import getter from './getters';
import actions from './action';

// 模块管理
import moduleA from './modules/moduleA.js';
import moduleB from './modules/moduleB.js';

Vue.use(Vuex);

const store = new Vuex.Store({
  state,
  mutations,
  getter,
  actions,
  modules: {
    moduleA,
    moduleB,
  },
});
export default store;

state.js

const state = {
  count = 0;
}

export default state;

mutation-types.js

//mutation-type(我们会将所有mutations的函数名放在这个文件里)
export const COUNTADD = 'COUNTADD'; // 递增count
export const COUNTRESET = 'COUNTRESET'; // 重置count

mutations.js

import * as types from './mutation-types';

const mutations = {
  // payload 官方定义为载荷,本质就是执行时传入的参数
  [types.COUNTADD](state, payload) {
    state.count++;
  },
  [types.COUNTRESET](state, payload) {
    state.count = payload;
  },
};

export default mutations;

action.js

import * as types from './mutation-types';
const action = {
  handleResetCount({ commit, dispatch, state, rootState }, payload) {
    commit(types.COUNTRESET, 0);
  },
};

getters.js

const getters = {};

export default getters;

modules

  • moduleA
import Axios from 'axios';

export default {
  // 命名空间
  namespaced: true,
  /* 存储数据 */
  state: {
    dataA: {},
  },
  /* 方法 */
  mutations: {
    handleDataA(state, payload) {
      state.dataA = payload;
    },
  },
  /* 计算属性 */
  getters: {},
  /* 异步方法,(如ajax) */
  actions: {
    async _todoA({ commit }, payload) {
      const res = await Axios.get('apiA');
      commit('handleDataA', res);
    },
  },
};
  • moduleB
export default {
  // 命名空间
  namespaced: true,
  /* 存储数据 */
  state: {},
  /* 方法 */
  mutations: {},
  /* 计算属性 */
  getters: {},
  /* 异步方法,(如ajax) */
  actions: {},
};

组件调用

<template>
  <div>
    <button @click="clickTest">清空</button>
  </div>
</template>
import { mapState, mapMutations, mapGetters, mapActions } from 'vuex';

export default {
  data(){
    return {
      lists:[],
    }
  },
  mounted(){},

  methods: {
    // 全局mutations调用 this.rootMutations()
    ...mapMutations(['rootMutations'])
    // moduleA 为module的命名空间
    // Mutations
    ...mapMutations('moduleA', [
      handleDataA:'handleDataA', // 或者 'handleDataA'
    ]),
    // Actions
    ...mapActions('moduleA', ['_todoA']), // this._todoA();

    // lists 清空
    clickTest(){
      this.handleDataA([])
    }
  },
  computed: {
    // 全局state调用 this.rootState
    ...mapState(['rootState']),
    // moduleA 为module的命名空间
    ...mapState('moduleA', ['dataA']),
    ...mapGetters('moduleA', []),
  },
  watch:{
    dataA:{
      handler(newVal){
        this.lists = newVal
      },
      deep:true
    }
  }
};

vuex Mutation

只有 mutation 能动 State

更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。Vuex 中的 mutation 非常类似于事件:每个 mutation 都有一个字符串的 事件类型 (type) 和 一个 回调函数 (handler)。这个回调函数就是我们实际进行状态更改的地方,并且它会接受 state 作为第一个参数:

const store = new Vuex.Store({
  state: {
    count: 1,
  },
  mutations: {
    // 事件类型 type 为 increment
    increment(state) {
      // 变更状态
      state.count++;
    },
  },
});

注意,我们不能直接 store.mutations.increment() 来调用,Vuex 规定必须使用 store.commit 来触发对应 type 的方法:

store.commit('increment');

传参

我们还可以向 store.commit 传入额外的参数:

mutations: {
  increment (state, n) {
    state.count += n
  }
}

// 调用
store.commit('increment', 10)

mutation 中的这个额外的参数,官方给它还取了一个高大上的名字:载荷(payload)

大多数情况下,载荷是一个对象,能够让我们更加易读:

mutations: {
  increment (state, payload) {
    state.count += payload.amount
  }
}

关于提交的方式,有两种:

// 1、把载荷和type分开提交
store.commit('increment', {
  amount: 10,
});

// 2、整个对象都作为载荷传给 mutation 函数
store.commit({
  type: 'increment',
  amount: 10,
});

修改规则

简单修改基础类型的状态数据倒是简单,没什么限制,但是如果修改的是对象,那就要注意了。比如这个例子:

const store = new Vuex.Store({
  state: {
    student: {
      name: '小明',
      sex: '女',
    },
  },
});

这个时候,我们如果想要给 student 添加一个年龄 age: 18 属性,怎么办呢?

是的,直接在 sex 下面把这个字段加上去不就行了,能这样当然最好了。但是如果我们要动态的修改呢?那就得遵循 Vue 的规则了。如下:

mutations: {
  addAge (state) {
    state.student = { ...state.student, age: 18 }
  }
}

以上就是给对象添加属性的两种方式,当然,对于已添加的对象,如果想修改具体值的话,直接更改就是,比如 state.student.age=20 即可。

至于为什么要这样,之前我们了解过,因为 store 中的状态是响应式的,当我们更改状态数据的时候,监视状态的 Vue 组件也会自动更新,所以 Vuex 中的 mutation 也需要与使用 Vue 一样遵守这些规则。

使用常量

就是使用常量来替代 mutation 事件的名字。

// mutation-types.js
export const SOME_MUTATION = 'SOME_MUTATION';
// store.js
import Vuex from 'vuex'
import { SOME_MUTATION } from './mutation-types'

const store = new Vuex.Store({
  state: { ... },
  mutations: {
    // 使用 ES2015 风格的计算属性命名功能来使用一个常量作为函数名
    [SOME_MUTATION] (state) {
      // mutate state
    }
  }
})

可能有人会有疑问啊,这样做到底有啥用,还得多创建个类型文件,用的时候还要导入进来,不嫌麻烦吗!

我们看看,mutation 是怎么调用的:store.commit('increment'),可以发现,这里 commit 提交的方法 increment,是以字符串的形式代入的。如果项目小,一个人开发的话倒还好,但是项目大了,编写代码的人多了,那就麻烦了,因为需要 commit 的方法一多,就会显得特别混乱,而且以字符串形式代入的话,一旦出了错,很难排查。

所以,对于多人合作的大项目,最好还是用常量的形式来处理 mutation,对于小项目倒是无所谓,想偷懒的随意就好。

必须是同步函数

一定要记住,Mutation 必须是同步函数。为什么呢?

前面说了,我们之所以要通过提交 mutation 的方式来改变状态数据,是因为我们想要更明确地追踪到状态的变化。如果像下面这样异步的话:

mutations: {
  someMutation (state) {
    api.callAsyncMethod(() => {
      state.count++
    })
  }
}

我们就不知道什么时候状态会发生改变,所以也就无法追踪了,这与 Mutation 的设计初心相悖,所以强制规定它必须是同步函数。

store.commit('increment');
// 任何由 "increment" 导致的状态变更都应该在此刻完成。

参考链接:https://www.jianshu.com/p/64727454f151


vuex 调用其他 modules 的 state, actions, mutations, getters

由于 Vuex 使用了单一状态树,应用的所有状态都包含在一个大对象中。那么,随着应用的不断扩展,store 会变得非常臃肿。

为了解决这个问题,Vuex 允许我们把 store 分 module(模块)。每一个模块包含各自的状态、mutation、action 和 getter。

那么问题来了, 模块化+命名空间之后, 数据都是相对独立的, 如果想在模块 A 调用 模块 B 的state, actions, mutations, getters, 该肿么办?

moduleA:

export default {
  // 命名空间
  namespaced: true,
  /* 存储数据 */
  state: {
    dataA: {},
  },
  /* 方法 */
  mutations: {
    handleDataA(state, payload) {
      state.dataA = payload;
    },
  },
  /* 计算属性 */
  getters: {},
  /* 异步方法,(如ajax) */
  actions: {
    async _todoA({ commit }, payload) {},
  },
};

moduleB:

export default {
  // 命名空间
  namespaced: true,
  /* 存储数据 */
  state: {
    dataB: {},
  },
  /* 方法 */
  mutations: {
    handleDataB(state, payload) {
      state.dataB = payload;
    },
  },
  /* 计算属性 */
  getters: {},
  /* 异步方法,(如ajax) */
  actions: {
    async _todoB({ commit }, payload) {},
  },
};

1、假设模块 B 的 actions 里, 需要用模块 A 的 state 该怎么办?

actions: {
    async _todoB({commit, dispatch, state, rootState }, payload) {
      console.log(rootState) // 打印根 state
      console.log(rootState.dataA) // 打印其他模块的 state
    },
},

假设模块 B 的 actions 里, 需要调用模块 A(或根模块) 的 actions 该怎么办?

actions: {
    async _todoB({commit, dispatch, state, rootState }, payload) {
      dispatch('moduleA/_todoA',{},{root:true}) // 调用其他模块的 actions
      dispatch('rootActions',{},{root:true}) // 调用根模块的 actions
    },
},

如上代码中dispatch('moduleA/_todoA',{},{root:true})有 3 个参数, 第一个参数是其他模块的 actions 路径, 第二个是传给 actions 的数据, 如果不需要传数据, 也必须预留, 第三个参数是配置选项, 申明这个 acitons 不是当前模块的

假设模块 B 的 actions 里, 需要调用模块 A(或根模块) 的 mutations 该怎么办?

actions: {
    async _todoB({commit, dispatch, state, rootState }, payload) {
      commit('moduleA/handleDataA',{},{root:true}) // 调用其他模块的 mutations
      commit('rootMutations',{},{root:true}) // 调用根模块的 mutations
    },
},

假设模块 B 的 actions 里, 需要用模块 A 的 getters 该怎么办?

actions: {
    async _todoB({commit, dispatch, state, rootState,rootGetters }, payload) {
      console.log(rootGetters['moduleA/gettersFn']) // 打印其他模块的 getters
    },
},

rootGetters 就是 vuex 中所有的 getters, 你可以用 rootGetters['xxxxx'] 来取其他模块的 getters

参考链接:https://segmentfault.com/a/1190000009434398

总结

以上就是关于vuex的整体使用,初学者可以慢慢消化。


版权声明:本文为qq_40031303原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。