vuex

346 阅读6分钟

1、认识vuex

  vuex是适用于在Vue项目开发时使用的状态管理工具。试想一下,如果在一个项目开发中频繁的使用组件传参的方式来同步data中的值,一旦项目变得很庞大,管理和维护这些值将是相当棘手的工作。为此,Vue为这些被多个组件频繁使用的值提供了一个统一管理的工具——vuex。在具有vuex的Vue项目中,我们只需要把这些值定义在vuex中即可,以便在整个Vue项目的组件中使用。

2、vuex安装

npm install vuex@next --save

3、使用

3.1、初始化store文件夹下的index.js文件

import { createStore } from "vuex";
//创建一个新的store实例
const store = createStore({
  state: {
    //存放的键值对就是要管理的状态
    name: "hello vuex"
  },
  mutations: {},
  actions: {},
  modules: {},
});

export default store;

3.2、挂载到Vue实例中

  让所有的Vue组件都可以使用这个store对象,来到main.js文件,导入store对象,并且放在Vue实例中,这样,在其他Vue组件中,我们就可以通过$store的方式,获取到这个store对象了。

import { createApp } from 'vue'
import App from './App.vue'
import store from './store'

createApp(App).use(store).mount('#app')

3.3、在组件中使用vuex

  例如在App.vue中,我们要将state中定义的name拿来在h1标签中显示:

<template>
  <div id="app">
    name:<h1>{{$store.state.name}}</h1>
  </div>
  <router-view/>
</template>

  或者在组件方法中使用:

<template>
<div id="nav">
  <button @click="name()">按钮</button>
</div>
</template>
<script>
import {useStore} from 'vuex';
export default {
setup() {
  const store = useStore();
  const name = ()=>{
    console.log(store.state.name);
  };
  return {
    name
  }   
},
}
</script>

  我们对使用步骤,做一个简单的小节:

  1. 提取出一个公共的store对象,用于保存在多个组件中共享的状态。
  2. 将store对象放置在Vue实例中,这样可以保证在所有的组件中都可以使用到。
  3. 在其他组件中使用store对象中保存的状态即可。

4、Vuex核心概念

  Vuex有几个比较核心的概念:

-   State
-   Getters
-   Mutation
-   Action
-   Module

4.1、State单一状态树

  Vuex提出使用单一状态树, 什么是单一状态树呢?

  我们来看一个生活中的例子。在国内我们有很多的信息需要被记录,比如上学时的个人档案,工作后的社保记录,公积金记录,结婚后的婚姻信息,以及其他相关的户口、医疗、文凭、房产记录等等(还有很多信息)。这些信息被分散在很多地方进行管理,有一天你需要办某个业务时(比如入户某个城市),你会发现你需要到各个对应的工作地点去打印、盖章各种资料信息,最后到一个地方提交证明你的信息无误。这种保存信息的方案,不仅仅低效,而且不方便管理,以及日后的维护也是一个庞大的工作(需要大量的各个部门的人力来维护,当然国家目前已经在完善我们的这个系统了)。

  这个和我们在应用开发中比较类似。如果你的状态信息是保存到多个Store对象中的,那么之后的管理和维护等等都会变得特别困难。所以Vuex也使用了单一状态树来管理应用层级的全部状态。单一状态树能够让我们最直接的方式找到某个状态的片段,而且在之后的维护和调试过程中,也可以非常方便的管理和维护。

4.2、Getters基本使用

  可以对state的成员加工后传递给外界:

import { createStore } from "vuex";
//创建一个新的store实例
const store = createStore({
  state: {
    student:[
      {id:10, name:'zhangsan', age:18},
      {id:11, name:'lisi', age: 22},
      {id:12, name:'wangwu', age: 23},
      {id:13, name: 'zhaoliu', age:24}]
  },
  mutations: {},
  actions: {},
  modules: {},
});

export default store;

  获取学生年龄大于20的个数。我们可以在Store中定义getters

  getters: {
    greaterAgeCount(state) {
      return state.students.filter(s => s.age > 20).length;
    }
  }

  在组件中调用:

$store.getters.greaterAgeCount

4.2.1、Getters作为参数和传递参数

  Getters中的方法有两个默认参数:

    1. state:当前vuex对象中的状态对象。
    1. getters:当前getters对象,用于将getters下其他getter拿来用。

  如果我们已经有了一个获取所有年龄大于20岁学生列表的getters, 那么代码可以这样来写

  getters: {
    greaterAgeStus(state) {
      return state.students.filter(s => s.age > 20);
    },
    greaterAgeCount(state, getters){
      return getters.greaterAgeStus.length
    }
  }

4.2.2、Mutation状态更新

  mutations是操作state数据的方法的集合,比如对该数据的修改、增加、删除等等。

  mutation的定义方式:

import { createStore } from "vuex";
//创建一个新的store实例
const store = createStore({
state: {
  count: 1
},
getters:{},
mutations: {
  increment(state){
    state.count ++;
  }
},
actions: {},
modules: {},
});

export default store;

  通过 mutation 更新:

<template>
  <div id="nav">
    <button @click="increment">增加</button>{{$store.state.count}}
  </div>
</template>
<script>
import {useStore} from 'vuex';
export default {
  setup() {
    const store = useStore();
    const increment = ()=>{
      store.commit('increment');
    };
    return {
      increment
    }   
  },
}
</script>

4.2.3、Mutation传递参数

  在通过mutation更新数据的时候, 有可能我们希望携带一些额外的参数,参数被称为是mutation的载荷(Payload)。

  Mutation中的代码:

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

  但是如果参数不是一个呢? 这个时候, 我们通常会以对象的形式传递, 也就是payload是一个对象。

  mutations:{
    increment(state, payload){
      state.count += payload.count;
    }
  }
<script>
import { useStore } from "vuex";
export default {
  setup() {
    const store = useStore();
    const increment = () => {
      store.commit('increment', {'count': 3})
    };
    return {
      increment
    };
  }
};
</script>

4.2.4、Mutation提交风格

  上面的通过commit进行提交是一种普通的方式。Vue还提供了另外一种风格, 它是一个包含type属性的对象。

<script>
import { useStore } from "vuex";
export default {
  setup() {
    const store = useStore();
    const increment = () => {
      store.commit({
        type: 'increment',
        count: 20
      })
    };
    return {
      increment
    };
  }
};
</script>

  Mutation中的处理方式是将整个commit的对象作为payload使用, 所以代码没有改变, 依然如下:

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

4.3、Action

4.3.1、Action的基本定义

  我们强调, 不要再Mutation中进行异步操作,但是某些情况, 我们确实希望在Vuex中进行一些异步操作, 比如网络请求, 必然是异步的. 这个时候怎么处理呢? Action类似于Mutation, 但是是用来代替Mutation进行异步操作的。

  Action的基本使用代码如下:

const store = createStore({
  state: {
    count: 0
  },
  mutations:{
    increment(state){
      state.count += 1;
    }
  },
  actions:{
    increment(context){
      context.commit('increment');
    }
  }
})

  context是什么?

  • context是和store对象具有相同方法和属性的对象。
  • 也就是说, 我们可以通过context去进行commit相关的操作, 也可以获取context.state等。
  • 但是注意, 这里它们并不是同一个对象, 为什么呢? 我们后面学习Modules的时候, 再具体说。

  这样的代码是否多此一举呢?

  • 我们定义了actions, 然后又在actions中去进行commit, 这不是脱裤放屁吗?
  • 事实上并不是这样, 如果在Vuex中有异步操作, 那么我们就可以在actions中完成了。

4.3.2、Action的分发

  在Vue组件中, 如果我们调用action中的方法, 那么就需要使用dispatch。

<script>
import { useStore } from "vuex";
export default {
  setup() {
    const store = useStore();
    const increment = () => {
      store.dispatch('increment');
    };
    return {
      increment
    };
  }
};
</script>

  同样的, 也是支持传递payload

<script>
import { useStore } from "vuex";
export default {
  setup() {
    const store = useStore();
    const increment = () => {
      store.dispatch('increment', {'cCount': 6});
    };
    return {
      increment
    };
  }
};
</script>
const store = createStore({
  state: {
    count: 0
  },
  mutations:{
    increment(state, playload){
      state.count += playload.cCount;
    }
  },
  actions:{
    increment(context, playload){
      setTimeout(()=>{
        context.commit('increment', playload);
      }, 1000);
    }
  }
})

4.3.3、Action返回的Promise

  前面我们学习ES6语法的时候说过, Promise经常用于异步操作。

  在Action中, 我们可以将异步操作放在一个Promise中, 并且在成功或者失败后, 调用对应的resolve或reject。

  我们来看下面的代码:

const store = createStore({
  state: {
    count: 0
  },
  mutations: {
    increment(state, playload) {
      state.count += playload.cCount;
    }
  },
  actions: {
    increment(context, playload) {
      return new Promise((resolve) => {
        setTimeout(() => {
          context.commit('increment', playload);
          resolve();
        }, 1000);
      })
    }
  }
})
<script>
import { useStore } from "vuex";
export default {
  setup() {
    const store = useStore();
    const increment = () => {
      store.dispatch('increment', {'cCount': 6}).then(()=>{
        console.log("完成了更新操作");
      }
      );
    };
    return {
      increment
    };
  }
};
</script>

4.4、Module

4.4.1、认识Module

  Module是模块的意思, 为什么在Vuex中我们要使用模块呢? Vue使用单一状态树,那么也意味着很多状态都会交给 Vuex来管理。当应用变得非常复杂时,store对象就有可能变得相当臃肿。为了解决这个问题, Vuex允许我们将store分割成模块(Module), 而每个模块拥有自己的state、mutations、actions、getters等。

  我们按照什么样的方式来组织模块呢?

import { createStore } from 'vuex'
const moduleA = {
  state: {},
  mutations: {},
  actions: {},
  getters: {}
}
const moduleB = {
  state: {},
  mutations: {},
  actions: {},
  getters: {}
}
//创建一个新的store实例
const store = createStore({
  modules:{
    a: moduleA,
    b: moduleB
  }
})
export default store;

4.4.2、Module局部状态

  上面的代码中, 我们已经有了整体的组织结构, 下面我们来看看具体的局部模块中的代码如何书写。

  • 我们在moduleA中添加state、mutations、getters。
  • mutation和getters接收的第一个参数是局部状态对象。
import { createStore } from 'vuex'
const moduleA = {
 state: {
   count: 0
 },
 mutations: {
   increment(state){
     state.count += 1;
   }
 },
 actions: {
   increment(context){
     setTimeout(() => {
       context.commit('increment');
     }, 1000);
   }
 },
 getters: {
   doubleCount(state){
     return state.count * 2;
   }
 }
}
const moduleB = {
}
//创建一个新的store实例
const store = createStore({
 state:{
   gCount: 111
 },
 modules:{
   a: moduleA,
   b: moduleB
 }
})

export default store;
<template>
 <div id="app">
   <h3>count:{{$store.state.a.count}}</h3>
   <h3>doubleCount:{{$store.getters.doubleCount}}</h3>
   <button @click="increment">增加</button>
 </div>
</template>
<script>
import { computed } from '@vue/runtime-core';
import { useStore } from "vuex";
export default {
 setup() {
   const store = useStore();
   const doubleCount = computed(()=>{
     return store.getters.doubleCount;
   });
   const increment = () => {
     store.dispatch('increment');
   };
   return {
     increment,
     doubleCount
   };
 }
};
</script>

5、项目结构组织

5.1.项目结构

  • 当我们的Vuex帮助我们管理过多的内容时, 好的项目结构可以让我们的代码更加清晰。 image-20210101193044224