安装开始

npm install vuex@next --save

创建 store.js文件,且仅需提供一个初始state对象和mutation:

import { createStore } from 'vuex'

// 创建一个新的 store 实例
const store = createStore({
 state: {
    // 定义一个name,以供全局使用
    name: '张三',
    // 定义一个number,以供全局使用
    number: 0,
    // 定义一个list,以供全局使用
    list: [
      { id: 1, name: '111' },
      { id: 2, name: '222' },
      { id: 3, name: '333' },
    ],
  },
});

export default store

在main.js中导入

import { createApp } from 'vue';
import App from './App.vue';
//1.导入sotore
import store from './store.js';

const app = createApp(App);
//2.挂载
app.use(store);
app.mount('#app');

此时,vuex已经可以在你的项目中使用了。

<template>
  <div></div>
</template>

<script>
export default {
  mounted() {
    // 使用this.$store.state.XXX可以直接访问到仓库中的状态
    console.log(this.$store.state.name);
  },
};
</script>

运行项目,可以在控制台看到我们在store中的name值。

vuex使用

接上文,官方建议,将 this.$store.state.xxx放到计算属性中,所以我们可以这样写:

export default {
  mounted() {
    console.log(this.getName);
  },
  computed: {
    getName() {
      return this.$store.state.name;
    },
  },
};

但是,每次都写 this.$store.state.xxx真的很烦,所以可以这么写:

import { mapState } from 'vuex'; // 从vuex中导入mapState
export default {
  mounted() {
    console.log(this.name);
  },
  computed: {
    ...mapState(['name']), // 经过解构后,自动就添加到了计算属性中,此时就可以直接像访问计算属性一样访问它
  },
};

当然,在解构的时候也可以给他一个名字,方便使用:

...mapState({ aliasName: 'name' }),  // 赋别名的话,这里接收对象,而不是数组

接下来我们更深入一点:先看vuex给的使用周期图:

图解:先找虚线框,虚线框里面的就是vuex,虚线框里面有一个state就是我们的数据,而要修改数据需要走如下的流程:

  1. 在组件里面调用Dispatch()方法提交Actions
  2. Actions再通过commit()方法提交Mutations
  3. 通过Mutations里面的方法改变state(数据)
  4. 响应(渲染)到组件里面

简单理解:

state:存数据的地方,所有的数据都要存在state里面。

Actions:与Mutations类似,包含的都是一些方法,不同的是Actions不能直接修改数据,它的作用是提交Mutations,Mutations里面的包含的才是具体操作数据的方法。

Mutations:唯一能修改数据的方法就是提交mutation,简单来说它里面存的就是一些操作数据的方法。

修饰器:Getter

修饰器,很简单理解就是修饰数据用的,比如,我们要在所有的name前面加上"Hello!",就可以用到修饰器了,首先,在store对象中增加getters属性:

 getters: {
    getMessage(state) {
      // 获取修饰后的name,第一个参数state为必要参数,必须写在形参上
      // state就是你的数据,不要多想
      return `hello${state.name}`;
    },
  },

然后,在组件中使用:

  mounted() {
    // 注意不是$store.state了,而是$store.getters
    console.log(this.$store.state.name);//张三
    console.log(this.$store.getters.getMessage);//hello张三
  },

如果你嫌这么写麻烦,官方又给了你建议:我们可以使用mapGetters去解构到计算属性中,就像使用mapState一样,就可以直接使用this调用了,就像下面这样:

import { mapState, mapGetters } from 'vuex';
export default {
  mounted() {
    console.log(this.name);
    console.log(this.getMessage);
  },
  computed: {
    ...mapState(['name']),
    ...mapGetters(['getMessage']),
  },
};

这么些得到的效果是和之前一样的,当然也可以取别名。

说到底,getters也是读取数据,只不过是修饰的。

修改值:Mutation

首先,必须说一下,store仓库里面的数据你可以随便拿,但是不能随便改,例如下面这种:

// 错误示范
this.$store.state.XXX = XXX;

如何修改数据,就要用到我们的Mutation,比如,我们先输出state中的number的值是0,然后我们在vue组件中提交mutations改变number的值为我们想要修改的值再输出:

在store.js中增加mutations如下:

mutations: {
    // 增加nutations属性
    setNumber(state) {
      // 增加一个mutations的方法,方法的作用是让num从0变成5,state是必须默认参数
      state.number = 5;
    },
  },

不要着急,这样并不会生效,你需要在组件中提交一下:

export default {
  mounted() {
    console.log(`旧值:${this.$store.state.number}`);//0
    this.$store.commit('setNumber');//只有commit提交之后才会修改
    console.log(`新值:${this.$store.state.number}`);//5
  },
};

这是简单实现mutations的方法,没有传参,如果想传不固定的参数,请继续往下看:

mutations: {
    setNumber(state) {
      state.number = 5;
    },
    setNumberIsWhat(state, number) {
      // 增加一个带参数的mutations方法
      state.number = number;
    },
  },

然后在组件中提交的时候也要带上参数:

export default {
  mounted() {
    console.log(`旧值:${this.$store.state.number}`);//0
    this.$store.commit('setNumberIsWhat', 666);
    console.log(`新值:${this.$store.state.number}`);//666
  },
};

注意:这么传参,我会经常写错,还是听官方的传递一个对象进去来:

mutations: {
    setNumber(state) {
      state.number = 5;
    },
    setNumberIsWhat(state, payload) {
      // 增加一个带参数的mutations方法,并且官方建议payload为一个对象
      state.number = payload.number;
    },
  },

组件中也需要修改一下:

export default {
  mounted() {
    console.log(`旧值:${this.$store.state.number}`);
    this.$store.commit('setNumberIsWhat', { number: 666 }); // 调用的时候也需要传递一个对象
    console.log(`新值:${this.$store.state.number}`);
  },
};

这样,当你以后遇到复杂一点的传参是不是就会了!

注意:Mutations里面的函数必须是同步操作,不能包含异步操作!!!

但是,这种 this.$store.commit('xxx')的写法是不是还是很难受,我们还是来用解构(store中的写法不变,看组件操作):

import { mapMutations } from 'vuex';
export default {
  mounted() {
    this.setNumberIsWhat({ number: 999 });
     console.log(this.$store.state.count);//注意打印了一个对象包含999
  },
  methods: {
    // 注意,mapMutations是解构到methods里面的,而不是计算属性了
    ...mapMutations(['setNumberIsWhat']),
  },
};

上面特别提醒过,mutations只能进行同步操作,下面就说一下异步。

异步操作:Actions

Actions存在的意义是假设你在修改state的时候有异步操作,vuex作者不希望你将异步操作放在Mutations中,所以就给你设置了一个区域,让你放异步操作,这就是Actions.

const store = createStore({
  state: {
    name: '张三',
    number: 0,
  },
  mutations: {
    setNumberIsWhat(state, payload) {
      state.number = payload.number;
    },
  },
  actions: {
    // 增加actions属性
    setNum(content) {
      // 增加setNum方法,默认第一个参数是content,其值是复制的一份store
      return new Promise(resolve => {
        // 我们模拟一个异步操作,1秒后修改number为888
        setTimeout(() => {
          content.commit('setNumberIsWhat', { number: 888 });
          resolve();
        }, 1000);
      });
    },
  },
});

然后在组件中打印看看:

async mounted() {
  console.log(`旧值:${this.$store.state.number}`);//0
  await this.$store.dispatch('setNum');
  console.log(`新值:${this.$store.state.number}`);//1秒后打印新值888
},

这就说明:action就是提交mutation的,所有的异步操作必须在action中进行操作,最后去提交给mutation。

在它里面也可以如mutation一样传参:

actions: {
  setNum(content, payload) {
    // 增加payload参数
    return new Promise(resolve => {
      setTimeout(() => {
        content.commit('setNumberIsWhat', { number: payload.number });
        resolve();
      }, 1000);
    });
  },
},

然后在组件中传参:

async mounted() {
  console.log(`旧值:${this.$store.state.number}`);//0
  await this.$store.dispatch('setNum', { number: 666 });
  console.log(`新值:${this.$store.state.number}`);//666
},

如果你不想使用 this.$store.dispatch('XXX')这样的写法去调用action,你可以采用mapActions的方法,把它解构到methos中,然后用this直接调用:

import { mapActions } from 'vuex';
export default {
  methods: {
    ...mapActions(['setNum']), // 就像这样,解构到methods中
  },
  async mounted() {
    await this.setNum({ number: 123 }); // 直接这样调用即可
  },
};

官方建议:在actions中,方法的形参可以直接将commit解构出来,方便操作:

actions: {
  setNum({ commit }) {
    // 直接将content解构掉,解构出commit,下面就可以直接调用了
    return new Promise(resolve => {
      setTimeout(() => {
        commit('XXXX'); // 直接调用提交
        //免去这一步:content.commit('setNumberIsWhat', { number: payload.number });
        resolve();
      }, 1000);
    });
  },
},

action存在的本质就是处理异步,它其实就是mutation的上一级,在action这里处理完异步的一些操作后,修改state就交给mutation去做了。

vuex拆分

当我们的项目稍微有规格的话,都放在一个store.js文件中是不是会有成百上千行,然后你查找一些东西会很费解,当然这可能说的是你,我目前还没这水平,但是提前研究一下怎么拆分。

按属性拆分

一个store里面基本包含四个属性:

那我们就按照属性来拆分:

新建四个文件,分别是 state.js getters.js mutations.js actions.js

1.拆出来state放到state.js中:

// state.js

export const state = {
  name: '张三',
  number: 0,
  list: [
    { id: 1, name: '111' },
    { id: 2, name: '222' },
    { id: 3, name: '333' },
  ],
};

2.拆出来 getters放到 getters.js中:

// getters.js

export const getters = {
  getMessage(state) {
    return `hello${state.name}`;
  },
};

3.拆出来 mutations放到 mutations.js中:

// mutations.js

export const mutations = {
  setNumber(state) {
    state.number = 5;
  },
};

4.拆出来 actions放到 actions.js文件中:

// actions.js

export const actions = {
  setNum(content) {
    return new Promise(resolve => {
      setTimeout(() => {
        content.commit('setNumberIsWhat', { number: 888 });
        resolve();
      }, 1000);
    });
  },
};

5.组装到主文件里面

import { state } from './state'; // 引入 state
import { getters } from './getters'; // 引入 getters
import { mutations } from './mutations'; // 引入 mutations
import { actions } from './actions'; // 引入 actions

import { createStore } from 'vuex'

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

export default store;

这个按属性拆分其实呃,就是比较清晰了。

按功能拆分

接下来我们以另外一个维度去拆分store,按功能拆分,就是Module(模块)。

读一下官方文档,你可能就明白了,我们有一个总的store,再根据不同的功能,添加两个不同的store,每个store里面维护不同的state,及自己的actions/mutations/getters。

当我们有一个store.js之后,我们再增加一个新的store2.js:

// store2.js

const store2 = {
  state: {
    name: '我是store2',
  },
  mutations: {},
  getters: {},
  actions: {},
};

export default store2;

然后在store中引入我们新加的store2模块:

import { state } from './state'; // 引入 state
import { getters } from './getters'; // 引入 getters
import { mutations } from './mutations'; // 引入 mutations
import { actions } from './actions'; // 引入 actions
import store2 from './store2'; // 引入store2模块

import { createStore } from 'vuex'

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

export default store;

访问state,我们在组件中访问store2模块的state中的name,需要这么写:

<template>
  <div></div>
</template>

<script>
export default {
  mounted() {
    console.log(this.$store.state.store2.name); // 访问store2里面的name属性
  },
};
</script>

我们通过下面的代码可以了解到在不同的属性里是怎么访问 模块内的状态 或者 根状态

mutations: {
  changeName(state, payload) {
    // state 局部状态
    console.log(state);
    console.log(payload.where);
  },
},
getters: {
  testGetters(state, getters, rootState) {
    // state 局部状态
    console.log(state);
    // 局部 getters,
    console.log(getters);
    // rootState 根节点状态
    console.log(rootState);
  },
},
actions: {
  increment({ state, commit, rootState }) {
    // state 局部状态
    console.log(state);
    // rootState 根节点状态
    console.log(rootState);
  },
},

以上是对module的简单介绍,其实这里就是一种思想,分而治之,将复杂的进行拆分,可以更有效的管理。

更多相关信息,请查阅官方文档:https://vuex.vuejs.org/zh/guide/modules.html

本文内容借鉴自:

作者:三年没洗澡
链接:https://juejin.cn/post/6928468842377117709
来源:稀土掘金
End
最后修改:2022 年 11 月 08 日
如果觉得我的文章不错,请随手点赞~