vuex 基础

74 阅读5分钟

什么是 Vuex ?

官方的解释

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

个人总结

看了上面一段文字我想行大多数人都是晕头转向的,官方的话通常都是晦涩难懂的,因为需要保证权威性和正确性。所有我通过我自己的理解给大家简单的理解一下:

所谓的Vuex其实就是一个为Vue.js设计的数据仓库,就是把各个组件公用的数据放到一个仓库里面进行统一的管理,这样既使得非父子组件间的数据共享变得简单明了,也让程序变得更加可维护(将数据抽离了出来),而且只要仓库里面的数据发生了变化,在其他组件里面数据被引用的地方也会自动更新。

语法解释

state

注意:① state 是响应式的,当我们变更状态时,监视状态的 Vue 组件也会自动更新;② 因为 state 是响应式的,最好提前在你的 state 中初始化好所有所需属性,当需要在对象上添加新属性时,可以使用对象展开运算符。(例如:state.obj = { ...state.obj, newProp: 123 }

getter

const store = new Vuex.Store({
  state: {
    todos: [
      { id: 1, text: '...', done: true },
      { id: 2, text: '...', done: false }
    ]
  },
  getters: {
    doneTodos: state => {
      return state.todos.filter(todo => todo.done)
    }
  }
})
  • 语法:
  • getter:(state, getters) => { };(通过属性访问的定义方式)
  • 参数:1.getter: getter 函数名;2.state: 表示 store 的状态集;3.getters:可选,表示 getter 的集合,可以在一个 getter 中通过 getters 访问其他 getter。
  • store.getters.getter;(通过属性访问)
  • 参数:1.getter:表示 getter函数名。
  • getter:(state, getters) => (args) => {};(通过方法访问的定义方式)
  • 参数:1.getter: getter 函数名;2.state: 表示 store 的状态集;3.getters:可选,表示 getter 的集合,可以在一个 getter 中通过 getters 访问其他 getter;4.args:方法的参数。
  • store.getters.getter(args);(通过方法访问)
  • 参数:1.getter:表示 getter 函数名;2.args:getter 函数中所调用方法的参数。

注意:① getter 会暴露为 store.getters 对象,你可以以属性的形式访问这些值;② getter 在通过属性访问时是作为 Vue 的响应式系统的一部分缓存其中的;③ getter 在通过方法访问时,每次都会去进行调用,而不会缓存结果。

Vuex 允许我们在 store 中定义“getter”(可以认为是 store 的计算属性)。就像计算属性一样,getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。

mutation

const store = new Vuex.Store({
  state: {
    count: 1
  },
  mutations: {
    // 回调函数 (handler)
    increment (state) {
      // 变更状态
      state.count++
    }
  }
})
  • 语法:
  • handler( state, payload ){ };
  • 参数: ①. state:表示 store 的状态集; ②. payload:可选,表示 mutation 的提交载荷。
  • store.commit(type, payload); (载荷的提交方式)
  • 参数: ①. type:表示 mutation 的事件类型,通过 type 唤醒 handler,且 type 的名称与 handler 名称保持一致; ②. payload:表示 mutation 的提交载荷。
  • store.commit({ type: typeName, args }); (对象风格的提交方式)
  • 参数: ① { type: typeName, args }:这整个对象都作为载荷 payload 传给 mutation 函数;② typeName 表示 mutation 的事件类型;③ args:可选,传递的参数数据。

注意:① 每个 mutation 都有一个字符串的 事件类型 (type)  和 一个 回调函数 (handler) ;② 在大多数情况下,载荷 payload 应该是一个对象,这样可以包含多个字段并且记录的 mutation 会更易读;③ 建议使用常量代替 mutation 事件类型,可以使 linter 之类的工具发挥作用,同时把这些常量放在单独文件更易读。

action

const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment (state) {
      state.count++
    }
  },
  actions: {
    increment (context) {
      context.commit('increment')
    }
  }
})

Action 类似于 mutation,不同在于:

  1. Action 提交的是 mutation,而不是直接变更状态。
  2. Action 可以包含任意异步操作。
  • 语法:
  • handler( context, payload ){ };
  • 参数:① context:与 store 实例具有相同方法和属性,当我们在之后介绍到 Modules 时,你就知道 context 对象为什么不是 store 实例本身了;② payload:表示 action 的提交载荷。
  • store.dispatch(type, payload);(载荷的提交方式)
  • 参数:① type:表示 action 的事件类型;② payload:可选,表示 action 的提交载荷。
  • store.dispatch({ type: typeName, args });(对象风格的提交方式)
  • 参数: ① { type: typeName, args }:这整个对象都作为载荷 payload 传给 action 函数;② typeName 表示 action 的事件类型;③ args:可选,传递的参数数据。

注意:① mapActions 辅助函数将组件的 methods 映射为 store.dispatch 调用;② store.dispatch 可以处理被触发的 action 的处理函数返回的 Promise,并且 store.dispatch 仍旧返回 Promise,所以可以组合 action;③ 一个 store.dispatch 在不同模块中可以触发多个 action 函数。在这种情况下,只有当所有触发函数完成后,返回的 Promise 才会执行。

构建项目和引入 Vuex

vue2 + vuex3

1.安装 vuex: npm install vuex --save

2.引入 vuex
① 在src目录下新建一个store文件夹,并在store目录下新建index.js
② 在src/store/index.js里面使用vuex,添加如下代码:

import Vue from 'vue';
import Vuex from 'vuex';

Vue.use(Vuex); // 显式地通过 Vue.use() 来安装 Vuex

const store = new Vuex.Store({
    state: {
        count: 0,
    },
    mutations: {
        increment (state) {
            state.count++;
        };
    }
});

export default store; //导出store 

③ 在main.js中引入store,然后全局注入一下,这样就可以在任何一个组件里面使用它了。

/**
* 为了在 Vue 组件中访问 `this.$store` property, 你需要为 Vue 实例提供创建好的 store。
* Vuex 提供了一个从根组件向所有子组件,以 store 选项的方式 “注入” 该 store 的机制。
*/
import Vue from 'vue';
import App from './App';
import router from './router';
import store from './store';

Vue.config.productionTip = false;

new Vue({
  el: '#app',
  router,
  store,
  components: { App },
  template: '<App/>'
});

④ 使用 state,以下涉及 state 的多种写法。

// src/store/index.js:
import Vue from 'vue';
import Vuex from 'vuex';

Vue.use(Vuex);

const store = new Vuex.Store({
  state: {
    count: 10,
    name: '香蕉',
    cycle: '一年'
  }
});

export default store;

// src/App.vue:
<template>
  <div class="wrapper">
    <p>{{ countVal }}</p>
    <p>{{ $store.state.count }}</p>
    <p>解析后的count:{{ resolveCount }}</p>
    <p>品名:{{ name }}</p>
    <p>品名:{{ nameAlias }}</p>
    <p>生长周期:{{ cycle }}</p>
  </div>
</template>

<script>
import { mapState } from 'vuex';

export default {
  data() {
    return {};
  },
  computed: {
    ...mapState([
      // 映射 this.cycle 为 this.$store.state.cycle
      'cycle'
    ]), 
    ...mapState({
      name: (state) => state.name,
      // 传字符串参数 'name' 等同于 `state => state.name`
      nameAlias: 'name', 
      resolveCount(state) {
        return state.count + 20;
      }
    }),
    countVal() {
      return this.$store.state.count;
    }
  }
};
</script>

⑤ 使用 getter,以下涉及 getter 的多种写法。

// src/store/index.js:
import Vue from 'vue';
import Vuex from 'vuex';

Vue.use(Vuex);

const store = new Vuex.Store({
  state: {
    years: 1,
    todos: [
      { id: 1, name: '香蕉', done: true },
      { id: 2, name: '橘子', done: true },
      { id: 3, name: '苹果', done: false }
    ]
  },
  mutations: {},
  actions: {},
  getters: {
    getYears: (state) => {
      return state.years;
    },
    filterTodos: (state) => {
      return state.todos.filter((todo) => todo.done);
    },
    filterTodosCount: (state, getters) => {
      return getters.filterTodos.length;
    },
    getTodosById: (state) => (id) => {
      return state.todos.find((todo) => todo.id === id);
    }
  }
});

export default store;

// src/App.vue:
<template>
  <div class="wrapper">
    <p>{{ getYears }}</p>
    <p>{{ $store.getters.filterTodos }}</p>
    <p>{{ getFilterTodos }}</p>
    <p>{{ getFilterTodosCount }}</p>
    <p>{{ doneTodosCount }}</p>
    <p>{{ getTodosById }}</p>
  </div>
</template>

<script>
import { mapGetters } from 'vuex';

export default {
  data() {
    return {};
  },
  computed: {
    // 使用对象展开运算符将 getter 混入 computed 对象中
    ...mapGetters([
      'getYears'
    ]),
    ...mapGetters({
      // 将 getter 属性另取名为 `doneTodosCount`, 把 `this.doneTodosCount` 映射为 
      // `this.$store.getters.filterTodosCount`。
      doneTodosCount: 'filterTodosCount'
    }),
    // 以属性的形式访问
    getFilterTodos() {
      return this.$store.getters.filterTodos;
    },
    // 以属性的形式访问
    getFilterTodosCount() {
      return this.$store.getters.filterTodosCount;
    },
    // 以方法的形式访问
    getTodosById() {
      return this.$store.getters.getTodosById(2);
    }
  },
  methods: {}
};
</script>

⑥ 使用 mutation,以下涉及 mutation 的多种写法。

// src/store/index.js:
import Vue from 'vue';
import Vuex from 'vuex';

Vue.use(Vuex);

const store = new Vuex.Store({
  state: {
    count: 10,
  },
  mutations: {
    increment(state, params) {
      const { addVal } = params;
      state.count = state.count + addVal;
    },
    decrease(state, params) {
      const { decreaseVal } = params;
      state.count = state.count - decreaseVal;
    },
    multiple(state, params) {
      const { multipleVal } = params;
      state.count = state.count * multipleVal;
    }
  }
});

export default store;

// src/App.vue:
<template>
  <div class="wrapper">
    <p>{{ $store.state.count }}</p>
    <button @click="handleAdd">增加</button>
    <button @click="handleIncrease">增加2</button>
    <button @click="handleDecrease">减少</button>
    <button @click="handleReduce">减少2</button>
    <button @click="handleMultiple">倍数</button>
    <button @click="multiple({ multipleVal: 3 })">三倍</button>
  </div>
</template>

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

export default {
  data() {
    return {};
  },
  methods: {
    ...mapMutations([
      'decrease' // 1.将 `this.decrease()` 映射为 `this.$store.commit('decrease')`;
    ]),          // 2.将 `this.decrease(payload)` 映射为 `this.$store.commit('decrease', payload)`
                 // (`mapMutations` 也支持载荷)。
    ...mapMutations({
      reduce: 'decrease', // 将 `this.reduce()` 映射为 `this.$store.commit('decrease')`
      multiple: 'multiple'
    }),
    handleAdd() {
      this.$store.commit('increment', { addVal: 1 });
    },
    handleIncrease() {
      this.$store.commit({ type: 'increment', addVal: 1 });
    },
    handleDecrease() {
      this.decrease({ decreaseVal: 1 });
    },
    handleReduce() {
      this.reduce({ decreaseVal: 1 });
    },
    handleMultiple() {
      this.multiple({ multipleVal: 2 });
    }
  }
};
</script>

⑦ 使用 actions,以下涉及 action 的多种写法。

// src/store/index.js:
import Vue from 'vue';
import Vuex from 'vuex';

Vue.use(Vuex);

const store = new Vuex.Store({
  state: {
    count: 10,
  },
  mutations: {
    increment(state, params) {
      const { addVal } = params;
      state.count = state.count + addVal;
    },
    decrease(state, params) {
      const { decreaseVal } = params;
      state.count = state.count - decreaseVal;
    },
    multiple(state, params) {
      console.log(params);
      const { multipleVal } = params;
      state.count = state.count * multipleVal;
    }
  },
  actions: {
    async incrementAction(context, params) {
      const { commit } = context;
      commit('increment', params);
    },
    decreaseAction(context, params) {
      const { commit } = context;
      return new Promise((resolve) => {
        setTimeout(() => {
          commit({ type: 'decrease', ...params });
          resolve();
        }, 3000);
      });
    },
    multipleAction({ commit }, params) {
      commit('multiple', params);
    },
    getMultipleAction(context) {
      return new Promise((resolve) => {
        resolve(2);
      });
    },
    async handleComAction(context) {
      const { dispatch, commit } = context;
      await dispatch('decreaseAction', { decreaseVal: 1 });
      commit('multiple', await getDoubleData());
    }
  }
});

async function getDoubleData() {
  return new Promise((resolve) => {
    resolve({ multipleVal: 2 });
  });
}

export default store;

// src/App.vue:
<template>
  <div class="wrapper">
    <p>{{ $store.state.count }}</p>
    <button @click="handleAdd">增加</button>
    <button @click="handleIncrease">增加2</button>
    <button @click="handleDecrease">减少</button>
    <button @click="handleReduce">减少2</button>
    <button @click="handleMultiple">倍数</button>
    <button @click="multipleAction({ multipleVal: 3 })">三倍</button>
    <button @click="handleCom">先增加后翻倍</button>
  </div>
</template>

<script>
import { mapActions, mapState } from 'vuex';

export default {
  data() {
    return {};
  },
  methods: {
    ...mapActions([
      'handleComAction',
      'decreaseAction' // 1.将 `this.decreaseAction()` 映射为 
    ]),                // `this.$store.dispatch('decreaseAction')`; 2.将 
                       // `this.decreaseAction(amount)` 映射为 
    ...mapActions({    // `this.$store.dispatch('decreaseAction', payload)`(`mapMutations` 也支持载荷)。
      reduceAction: 'decreaseAction',   // 将 `this.reduceAction()` 映射为 
      multipleAction: 'multipleAction'  // `this.$store.dispatch('decreaseAction')`。
    }),

    handleAdd() {
      this.$store.dispatch('incrementAction', { addVal: 1 });
    },
    handleIncrease() {
      this.$store.dispatch({ type: 'incrementAction', addVal: 1 });
    },
    handleDecrease() {
      this.decreaseAction({ decreaseVal: 1 });
    },
    handleReduce() {
      this.reduceAction({ decreaseVal: 1 });
    },
    handleMultiple() {
      this.multipleAction({ multipleVal: 2 });
    },
    handleCom() {
      this.handleComAction();
    }
  }
};
</script>

Module(vuex 模块化)

如果模块没有开启命名空间:① 分发 mutation 或 action 的时候,不需要加模块前缀;② 如果不同模块中有 mutation 或 action 重名的情况,在分发 mutation 或 action 后,这个重名的 handler 函数都将根据模块列表的顺序依次触发;③ 通常我们会开启模块命名空间。

// src/store/index.js:
import Vue from 'vue';
import Vuex from 'vuex';
import moduleA from './modules/a';
import moduleB from './modules/b';

Vue.use(Vuex);

const store = new Vuex.Store({
  modules: {
    a: moduleA,
    b: moduleB
  }
});

export default store;
/**
* 注意: 1.默认 namespaced: false, 没有开启命名空间;
*/
// src/store/modules/a.js:
const moduleA = {
  state: {
    name: '模块A',
    count: 1,
    years: 1
  },
  mutations: {
    changeYearA(state) {
      const { val } = params;
      state.years = val;
    }
  },
  actions: {}
};

export default moduleA;
// src/store/modules/b.js:
const moduleB = {
  namespaced: true, // 开启了命名空间
  state: {
    name: '模块B',
    count: 2,
    years: 2
  },
  mutations: {
    changeYears(state, params) {
      const { val } = params;
      state.years = val;
    },
    changeName(state, params) {
      const { name } = params;
      state.name = name;
    },
    changeCount(state, params) {
      console.log(params);
      const { count } = params;
      state.count = count;
    }
  },
  actions: {
    changeYearsAction({ commit }, params) {
      commit('changeYears', params);
    }
  }
};

export default moduleB;
// src/App.vue:
<template>
  <div class="wrapper">
    <p>名称:{{ $store.state.a.name }}</p>
    <p>名称2:{{ $store.state.b.name }}</p>
    <p>数量: {{ a.count }}</p>
    <p>{{ a.name }}</p>
    <p>模块a的年限:{{ $store.state.a.years }}</p>
    <p>{{ name }}</p>
    <p>{{ b.count }}</p>
    <p>获取名称:{{ getName }}</p>
    <p>年限:{{ years }}</p>
    <p>获取年限:{{ getYears }}</p>
    <button @click="changeYears({ val: 3 })">更改年限</button>
    <button @click="handleName()">更改名称</button>
    <button @click="handleCount()">更改数量</button>
    <button @click="changeYearA">更改模块A的年限</button>
    <button @click="changeYA({ val: 6 })">更改模块A的年限2</button>
    <button @click="changeYearsAction({ val: 5 })">更改模块B的年限</button>
  </div>
</template>

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

export default {
  data() {
    return {};
  },
  computed: {
    ...mapState(['a']),
    ...mapState({
      name: (state) => state.a.name
    }),
    getName() {
      return this.$store.state.a.name;
    },
    ...mapState(['b']),
    // 第一个参数是模块的空间名称字符串
    ...mapState('b', {
      years: (state) => state.years
    }),
    getYears() {
      return this.years;
    }
  },
  methods: {
    ...mapMutations({
      changeYA: 'changeYearA'
    }),
    ...mapMutations('b', [
      'changeYears' // -> this.changeYears()
    ]),
    ...mapMutations(['b/changeCount']), // -> this['b/changeCount']()
    ...mapActions('b', [
      'changeYearsAction' // -> this.changeYearsAction()
    ]),
    handleName() {
      this.$store.commit('b/changeName', { name: '还是模块B' });
    },
    handleCount() {
      this['b/changeCount']({ count: 5 });
    },
    changeYearA() {
      this.$store.commit('changeYearA', { val: 6 }); // a 模块没有开启命名空间,所以不用加模块名
    }
  }
};
</script>

使用 createNamespacedHelpers 创建基于某个命名空间辅助函数。它返回一个对象,对象里有新的绑定在给定命名空间值上的组件绑定辅助函数。

// src/App.vue:
<template>
  <div class="wrapper">
    <p>名称:{{ $store.state.a.name }}</p>
    <p>名称2:{{ $store.state.b.name }}</p>
    <p>数量: {{ a.count }}</p>
    <p>{{ a.name }}</p>
    <p>模块a的年限:{{ $store.state.a.years }}</p>
    <p>{{ name }}</p>
    <p>{{ b.count }}</p>
    <p>获取名称:{{ getName }}</p>
    <p>年限:{{ years }}</p>
    <p>获取年限:{{ getYears }}</p>
    <button @click="changeYears({ val: 3 })">更改年限</button>
    <button @click="handleName()">更改名称</button>
    <button @click="handleCount()">更改数量</button>
    <button @click="changeYearA">更改模块A的年限</button>
    <button @click="changeYA({ val: 6 })">更改模块A的年限2</button>
    <button @click="changeYearsAction({ val: 5 })">更改模块B的年限</button>
  </div>
</template>

<script>
import { mapMutations, mapState, createNamespacedHelpers } from 'vuex';
const b = createNamespacedHelpers('b');

export default {
  data() {
    return {};
  },
  computed: {
    ...mapState(['a']),
    ...mapState({
      name: (state) => state.a.name
    }),
    getName() {
      return this.$store.state.a.name;
    },
    ...mapState(['b']),
    ...b.mapState({
      years: (state) => state.years
    }),

    getYears() {
      return this.years;
    }
  },
  methods: {
    ...mapMutations({
      changeYA: 'changeYearA'
    }),
    ...b.mapMutations(['changeYears', 'changeCount', 'changeName']),
    ...b.mapActions(['changeYearsAction']),
    handleName() {
      this.changeName({ name: '还是模块B' });
    },
    handleCount() {
      this.changeCount({ count: 5 });
    },
    changeYearA() {
      this.$store.commit('changeYearA', { val: 6 });
    }
  }
};
</script>