• Vue全家桶 Vuex的详细介绍


    Vuex状态管理

    认识状态管理

    在开发中,我们会的应用程序需要处理各种各样的数据,这些数据需要保存在我们应用程序中的某一个位置,对于这些数据的管理我们就称之为是 状态管理

    在前面我们是如何管理自己的状态呢

    在Vue开发中,我们使用组件化的开发方式;

    而在组件中我们定义data或者在setup中返回使用的数据,这些数据我们称之为state

    在模块template中我们可以使用这些数据,模块最终会被渲染成DOM,我们称之为View

    在模块中我们会产生一些行为事件,处理这些行为事件时,有可能会修改state,这些行为事件我们称之为actions

    由于JavaScript开发的应用程序,已经变得越来越复杂了

    JavaScript需要管理的状态越来越多,越来越复杂;

    这些状态包括服务器返回的数据、缓存数据、用户操作产生的数据等等;

    也包括一些UI的状态,比如某些元素是否被选中,是否显示加载动效,当前分页;

    当我们的应用遇到多个组件共享状态时,单向数据流的简洁性很容易被破坏

    多个视图依赖于同一状态;

    来自不同视图的行为需要变更同一状态;

    我们是否可以通过组件数据的传递来完成呢

    对于一些简单的状态,确实可以通过props的传递或者Provide的方式来共享状态;

    但是对于复杂的状态管理来说,显然单纯通过传递和共享的方式是不足以解决问题的,比如兄弟组件如何共享数据呢?


    Vuex的基本使用

    🍤Vuex的状态管理

    管理不断变化的state本身是非常困难的

    状态之间相互会存在依赖,一个状态的变化会引起另一个状态的变化,View页面也有可能会引起状态的变化;

    当应用程序复杂时,state在什么时候,因为什么原因而发生了变化,发生了怎么样的变化,会变得非常难以控制和追踪;

    因此,我们是否可以考虑将组件的内部状态抽离出来,以一个全局单例的方式来管理呢?

    在这种模式下,我们的组件树构成了一个巨大的 “视图View”;

    不管在树的哪个位置,任何组件都能获取状态或者触发行为

    通过定义和隔离状态管理中的各个概念,并通过强制性的规则来维护视图和状态间的独立性,我们的代码边会变得更加结构化和易于维护、跟踪;

    这就是Vuex背后的基本思想,它借鉴了Flux、Redux、Elm(Elm纯函数语言,redux有借鉴Elm的思想), 当然,目前Vue官方也在推荐使用Pinia进行状态管理,下一篇中也会进行讲解。

    在这里插入图片描述

    🍤Vuex的使用流程

    既然我们要使用vuex,首先第一步需要安装vuexnpm install vuex

    • 这里使用的是vuex4.x

    每一个Vuex应用的核心就是store(仓库)

    store本质上是一个容器,它包含着你的应用中大部分的状态(state);

    Vuex和单纯的全局对象有什么区别呢

    第一:Vuex的状态存储是响应式

    • 当Vue组件从store中读取状态的时候,若store中的状态发生变化,那么相应的组件也会被更新;

    第二:你不能直接改变store中的状态

    • 改变store中的状态的唯一途径就显示提交 (commit) mutation;
    • 这样使得我们可以方便的跟踪每一个状态的变化,从而让我们能够通过一些工具帮助我们更好的管理应用的状态;

    例如我们有下面这样一个计数器, 我们希望这个计数器的counter能抽离到store藏库里面, 让多个组件共享数据

    <template>
      <div class="app">
        <h2>当前计数: {{ counter }}h2>
      div>
    template>
    
    <script setup>
      import { ref } from 'vue';
    
      let counter = ref(0)
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    使用步骤

    1. 创建Store对象;

    2. 在app中通过插件安装;

    • 创建store对象
    // 1.导入createStore函数
    import { createStore } from "vuex";
    
    // 2.创建store对象
    const store = createStore({
      // state函数要求返回一个对象
      state: () => ({
        counter: 0
      })
    })
    
    export default store
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 在app中通过插件安装
    import { createApp } from 'vue'
    import App from './App.vue'
    
    // 1.导入状态管理store对象
    import store from "./store/index"
    
    const app = createApp(App)
    
    // 2.注册store
    app.use(store)
    
    app.mount('#app')
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    在组件中使用store,我们按照如下的方式

    1. 在模板中使用;
    2. 在options api中使用,比如computed;
    3. 在setup中使用;
    • 在要使用couter的组件模板中访问store: $store.state.counter
    <template>
      <div class="app">
        <h2>当前计数: {{ $store.state.counter }}h2>
      div>
    template>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 在options api中使用,比如computed
    export default {
      computed: {
        storeCounter() {
          return this.$store.state.counter
        }
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 在setup中使用
    <h2>当前计数: {{ counter }}h2>
    
    <script setup>
      import { toRefs } from 'vue';
      import { useStore } from 'vuex';
    
      // 拿到store对象
      const store = useStore()
    
      // 通过store获取counter
      // 1.直接解构的话会丢失响应式的
      // const { counter } = store.state.counter
      
      // 2.解构时包裹toRefs函数, 返回的是ref对象, 会保留响应式
      const { counter } = toRefs(store.state)
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    单一状态树

    Vuex 使用单一状态树

    用一个对象就包含了全部的应用程序的状态;

    采用的是SSOT,Single Source of Truth,也可以翻译成单一数据源

    这也意味着,每个应用将仅仅包含一个 store 实例

    单状态树和模块化并不冲突,后面我们会讲到module的概念;

    单一状态树的优势

    如果你的状态信息是保存到多个Store对象中的,那么之后的管理和维护等等都会变得特别困难;

    所以Vuex也使用了单一状态树来管理应用层级的全部状态;

    单一状态树能够让我们最直接的方式找到某个状态的片段;

    而且在之后的维护和调试过程中,也可以非常方便的管理和维护

    核心概念State

    🍟Options中获取状态

    在前面我们已经学习过如何在组件中获取状态了

    但是如果我们获取很多种状态, 那么会觉得那种方式有点繁琐(表达式过长),我们虽然可以使用计算属性, 但是同样需要创建多个函数, 再写多个表达式

    如果我们有很多个状态都需要获取话,其实 可以使用mapState的辅助函数

    mapState的方式一:数组类型;

    mapState的方式二:对象类型;

    也可以使用展开运算符和来原有的computed混合在一起

    方式一: 数组类型

    数组类型有个弊端, 要求数组中映射的名字和state中的名字一致

    如果data数据中也有相似的名称那么就会冲突

    <h2>{{ name }}h2>
    <h2>{{ age }}h2>
    <h2>{{ height }}h2>
    
    <script>
      import { mapState } from 'vuex';
    
      export default {
        computed: {
          // 数组的方式, 名称和state中的需要一一对应
          ...mapState(["name", "age", "height"])
        }
      }
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    方式二: 对象类型

    对象类型可以自定义名称, 属性值为一个函数, 固定的写法

    <h2>{{ sName }}h2>
    <h2>{{ sAge }}h2>
    <h2>{{ sHeight }}h2>
    
    <script>
      import { mapState } from 'vuex';
    
      export default {
        computed: {
          // 对象的方式, 可以自定义名称
          ...mapState({
            // 属性值为一个函数, 固定的写法
            sName: state => state.name,
            sAge: state => state.age,
            sHeight: state => state.height
          })
        }
      }
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    🍟setup中获取状态

    在setup中如果我们单个获取装是非常简单的

    通过useStore拿到store后去获取某个状态即可;

    但是如果我们需要使用 mapState 的功能呢?

    默认情况下,Vuex并没有提供非常方便的使用mapState的方式, 在setup中使用mapState函数是比较复杂的

    <h2>{{ aName }}h2>
    <h2>{{ aAge }}h2>
    <h2>{{ aHeight }}h2>
    
    <script setup>
      import { computed } from 'vue';
      import { mapState, useStore } from 'vuex';
    
      // 获取store对象
      const store = useStore()
    
      // 对返回的函数进行解构, 得到三个函数
      const {name, age, height} = mapState(["name", "age", "height"])
    
      // computed要求传入一个函数, 将解构的函数传入
      // 对解构的函数进行this绑定, 将this绑定到store上
      const aName = computed(name.bind({$store: store}))
      const aAge = computed(age.bind({$store: store}))
      const aHeight = computed(height.bind({$store: store}))
    
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    我们发现setup中使用mapState是非常麻烦的, 我们仅仅是想要获取到状态确这么麻烦

    因此在开发中不推荐大家在setup中使用mapState, 我们可以直接对store.state进行解构

    由于解构出来的数据不是响应式, 因此包裹一层toRefs()函数

    <h2>{{ name }}</h2>
    <h2>{{ age }}</h2>
    <h2>{{ height }}</h2>
    
    <script setup>
      import { toRefs } from 'vue';
      import { mapState, useStore } from 'vuex';
    
      // 获取store对象
      const store = useStore()
    	
      // 解构同时包裹一层toRefs函数
      const {name, age, height} = toRefs(store.state)
    </script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    核心概念Getters

    🍧getters基本使用

    某些属性我们可能需要经过变化后来使用,这个时候可以使用getters

    // 2.创建store对象
    const store = createStore({
      state: () => ({
        counter: 100,
        users: [
          {id: 111, name: "chenyq", age: 19},
          {id: 112, name: "kaisa", age: 20},
          {id: 113, name: "vn", age: 21}
        ]
      }),
      // getters对应一个对象
      getters: {
        // 1.将counter的两倍返回
        doubleCounter(state) {
          return state.counter * 2
        },
        // 2.将users中的年龄总和返回
        totalAge(state) {
          return state.users.reduce((preValue, item) => {
            return preValue + item
          }, 0)
        }
      }
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 拿到结果进行展示
    <h2>{{ $store.getters.doubleCounter }}h2>
    <h2>{{ $store.getters.totalAge }}h2>
    
    • 1
    • 2

    getters可以接收第二个参数getters

    例如返回一个name并且拼接上users的年龄总和

    const store = createStore({
      state: () => ({
        name: "lin yj",
        counter: 100,
        users: [
          {id: 111, name: "chenyq", age: 19},
          {id: 112, name: "kaisa", age: 20},
          {id: 113, name: "vn", age: 21}
        ]
      }),
      // getters对应一个对象
      getters: {
        // 1.将counter的两倍返回
        doubleCounter(state) {
          return state.counter * 2
        },
        // 2.将users中的年龄总和返回
        totalAge(state) {
          return state.users.reduce((preValue, item) => {
            return preValue + item.age
          }, 0)
        },
        // 3.name并且拼接上年龄总和
        message(state, getters) {
          // 由于年龄前面已经计算过一次了, 我们无需重新计算, 通过参数getters可以拿到
          return `${state.name}: ${getters.totalAge}`
        }
      }
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 拿到结果进行展示

    getters的返回函数

    getters中的函数本身,可以返回一个函数,那么在使用的地方相当于可以调用这个函数

    例如上面代码中在getters中实现一个根据id返回用户信息的函数

    const store = createStore({
      // state函数要求返回一个对象
      state: () => ({
        name: "lin yj",
        counter: 100,
        users: [
          {id: 111, name: "chenyq", age: 19},
          {id: 112, name: "kaisa", age: 20},
          {id: 113, name: "vn", age: 21}
        ]
      }),
      // getters对应一个对象
      getters: {
        getUserById(state) {
          // 返回一个函数
          return function(id) {
            const user = state.users.find(item => item.id === id)
            return user
          }
        }
      }
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 在使用的地方可以调用getUserById函数
    <h2>{{ $store.getters.getUserById(111) }}h2>
    <h2>{{ $store.getters.getUserById(112) }}h2>
    <h2>{{ $store.getters.getUserById(113) }}h2>
    
    • 1
    • 2
    • 3

    🍧getter辅助函数

    和state类似, getters也有一个辅助函数mapGetters

    Options API中使用辅助函数: 方便获取多个getters

    使用方法基本上和mapState相似, 大家看看就行, 不再过多介绍

      <h2>{{ message }}h2>
        <h2>{{ totalAge }}h2>
        <h2>{{ meMessage }}h2>
        <h2>{{ usersAge }}h2>
    
      div>
    template>
    
    <script>
      import { mapGetters } from 'vuex'
    
      export default {
        computed: {
          // 1.数组的形式
          ...mapGetters(["message", "totalAge"]),
          // 2.对象的形式
          ...mapGetters({
            meMessage: "message",
            usersAge: "totalAge"
          })
        }
      }
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    在setup中使用mapGetters就像使用mapState一样, 是非常繁琐的, 不推荐使用;

    使用的方法和mapState一样, 同样不再多说, 大家可以自行尝试

    和state一样, setup中不推荐使用mapGetters

    使用computd单独获取每一个元素

    <h2>{{ message }}h2>
    <h2>{{ totalAge }}h2>
    <h2>{{ getUserById(111) }}h2>
    
    <script setup>
      import { useStore } from 'vuex';
    
      import { computed } from 'vue';
    
      const store = useStore()
    
      const message = computed(() => store.getters.message)
      const totalAge = computed(() => store.getters.totalAge)
      const getUserById = computed(() => store.getters.getUserById)
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    核心概念Mutation

    🧁Mutation基本使用

    更改 Vuex 的 store 中的状态的唯一方法是提交 mutation

    比如, 我们如果想要修改store中的state中的counter属性, 我们并不能直接通过store.state.counter修改

    我们必须通过commit提交mutation, 在mutation中进行修改

    const store = createStore({
      state: () => ({
        counter: 100,
      }),
      // commit提交到mutations
      mutations: {
        increment(state) {
          state.counter++
        },
        decrement(state) {
          state.counter--
        }
      }
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    <h2>当前计数: {{ counter }}h2>
    <button @click="add">+button>
    <button @click="sub">-button>
    
    <script setup>
      import { useStore } from 'vuex';
    
      // 拿到store对象
      const store = useStore()
    
      function add() {
        // 通过commit提交到mutation
        store.commit("increment")
      }
    
      function sub () {
        store.commit("decrement")
      }
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    🧁Mutation携带数据

    很多时候我们在提交mutation的时候,会携带一些数据,这个时候我们可以使用参数

    例如修改state中的name属性时, 可以将修改的内容通过传递参数传出

    const store = createStore({
      state: () => ({
        name: "lin yj",
      }),
      // commit提交到mutations
      mutations: {
        // payload接收传入的参数
        changeName(state, payload) {
          state.name = payload
        }
      }
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    <h2>{{ $store.state.name }}h2>
    <h2>{{ $store.state.age }}h2>
    <h2>{{ $store.state.height }}h2>
    <button @click="changeName">修改namebutton>
    
    <script setup>
      import { useStore } from 'vuex';
    
      // 拿到store对象
      const store = useStore().commit("decrement")
    
      function changeName() {
        // 参数传入修改的数据
        store.commit("changeName", "张三炮")
      }
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    payload也可以传入为对象类型

    const store = createStore({
      state: () => ({
        name: "lin yj",
        age: 18,
        height: 1.88,name: "lin yj",
      }),
      // commit提交到mutations
      mutations: {
        // payload接收对象类型
        changeInfo(state, payload) {
          state.name = payload.name
          state.age = payload.age
          state.height = payload.height
        }
      }
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    <h2>{{ $store.state.name }}h2>
    <h2>{{ $store.state.age }}h2>
    <h2>{{ $store.state.height }}h2>
    <button @click="changeInfo">修改infobutton>
    
    <script setup>
      import { useStore } from 'vuex';
    
      // 拿到store对象
      const store = useStore().commit("decrement")
      }
    
      function changeInfo() {
        // 参数传入一个对象
        store.commit("changeInfo", {
          name: "王老五",
          age: 20,
          height: 1.58
        })
      }
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    🧁Mutation常量类型

    我们发现, commit提交和mutation中的名称是需要保持一致的, 由于这个名称又不会更改, 所在开发中, 我们通常会给这些名称定义一个常量, 并将它放到独立的js文件中去

    • 定义常量:mutation_type.js
    export const CHANGE_INFO = "CHANGE_INFO"
    
    • 1
    • 使用常量定义mutation
    // 导入常量
    import { CHANGE_INFO } from "./mutation_types";
    
    const store = createStore({
      state: () => ({
        name: "lin yj",
        age: 18,
        height: 1.88,name: "lin yj",
      }),
      mutations: {
        // 使用常量定义mutation
        [CHANGE_INFO](state, payload) {
          state.name = payload.name
          state.age = payload.age
          state.height = payload.height
        }
      }
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    使用常量提交mutation

    // 导入常量
    import { CHANGE_INFO } from "./store/mutation_types"
    
    function changeInfo() {
      // 参数传入一个对象
      store.commit([CHANGE_INFO], {
        name: "王老五",
        age: 20,
        height: 1.58
      })
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    🧁Mutation辅助函数

    我们也可以借助于辅助函数,帮助我们快速映射到对应的方法中

    • Options API中的使用
    <h2>当前计数: {{ counter }}h2>
    <button @click="increment">+button>
    <button @click="decrement">-button>
    
    <h2>{{ $store.state.name }}h2>
    <h2>{{ $store.state.age }}h2>
    <h2>{{ $store.state.height }}h2>
    <button @click="changeInfo({name: '王老五', age: 20, height: 1.33})">
      修改info信息
    button>
    
    <script>
      import { mapMutations } from "vuex";
      import { CHANGE_INFO } from "./store/mutation_types"
    
      export default {
        methods: {
          ...mapMutations(["increment", "decrement", CHANGE_INFO])
        }
      }
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • Setup中同样不推荐使用mapMutation

    🧁mutation重要原则

    Mutation有一条重要的原则就是: mutation中必须是同步函数

    这是因为devtool工具会记录mutation的日记;

    每一条mutation被记录,devtools都需要捕捉到前一状态和后一状态的快照;

    但是在mutation中执行异步操作,就无法追踪到数据的变化;

    所以Vuex的重要原则中要求 mutation必须是同步函数;

    但是如果我们希望在Vuex中发送网络请求的话需要如何操作呢?

    有事时候我们确实想要在Vuex中发送网络请求, 比如发送网络请求得到的数据是一些状态, 我们没必要在组件中发送网络请求再放到store中, 我们可以直接在store中发送网络请求

    那么遇到这种情况, 就是我们接下来要学习的Actions核心, Actions是专门处理异步操作的地方, 我们的异步代码都需要在Actions中处理


    核心概念Actions

    🍸Actions基本使用

    Action类似于Mutation,不同在于

    Action提交的是Mutation,而不是直接变更状态;也就是说, Action中想要修改state的话, 也需要提交Mutation, 不允许直接修改

    Action可以包含任意异步操作, Mutation中只能进行同步操作;

    mutations: {
      increment(state) {
        state.counter++
      }
    },
    actions: {
      incrementAction(context) {
        // action中修改state需要提交mutation
        context.commit("increment")
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    我们发现这里有一个非常重要的参数context

    context是一个和store实例均有相同方法和属性的context对象;

    所以我们可以从其中获取到commit方法来提交一个mutation,或者通过 context.statecontext.getters 来获取 state 和 getters;

    actions: {
      incrementAction(context) {
        // 提交mutation
        context.commit("increment")
        // 获取state中的状态
        context.state.name
        // 获取getter中的数据
        context.getters.doubleCounter
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    相信大家很疑惑为什么它不是store对象呢?这个在下面讲核心概念Modules时再具体来说

    🍸Actions分发操作

    Options中Action的分发

    如何使用action呢?进行action的分发

    分发使用的是 store 上的dispatch函数

    <h2>{{ counter }}h2>
    <button @click="add">按钮button>
    
    <script>
      export default {
        methods: {
          add() {
            // 分发到Action中
            this.$store.dispatch("incrementAction")
            console.log()
          }
        }
      }
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    同样的,它也可以携带我们的参数, 例如修改我们刚刚的Info

    • 定义Action
    actions: {
      // 接收参数payload提交到mutation
      changeNameAction(context, payload) {
        context.commit("changeName", payload)
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 携带参数分发
    <h2>{{ name }}h2>
    <button @click="changeName">改变Namebutton>
    
    <script>
      export default {
        methods: {
          changeName() {
            // 分发时携带参数
            this.$store.dispatch("changeNameAction", "罗三炮")
          }
        }
      }
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    也可以以对象的形式进行分发:

    • 定义Action
    actions: {
      // 接收参数payload提交到mutation
      changeInfoAction(context, payload) {
        context.commit(CHANGE_INFO, payload)
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 分发时传递对象作为参数
    <h2>{{ name }}h2>
    <h2>{{ age }}h2>
    <h2>{{ height }}h2>
    <button @click="changeInfo">改变Infobutton>
    
    <script>
      export default {
        methods: {
          changeInfo() {
            this.store.dispatch("changeInfoAction", {
              name: "王老五",
              age: 30,
              height: 1.55
            })
          }
        }
      }
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    setup函数中Action分发

    <script setup>
      import { useStore } from 'vuex';
    
      import { toRefs } from 'vue';
    
      const store = useStore()
    
      const {counter, name, age, height} = toRefs(store.state)
    
      // 分发incrementAction
      function add() {
        store.dispatch("incrementAction")
      }
    
      // 分发changeNameAction
      function changeName() {
        store.dispatch("changeNameAction", "罗三炮")
      }
    
      // 分发changeInfoAction
      function changeInfo() {
        store.dispatch("changeInfoAction", {
          name: "王老五",
          age: 40,
          height: 1.56
        })
      }
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    🍸Actions辅助函数

    action也有对应的辅助函数

    对象类型的写法;

    数组类型的写法;

    • 数组类型的写法
    <h2>{{ counter }}h2>
    <button @click="incrementAction">按钮button>
    <h2>{{ name }}h2>
    <button @click="changeNameAction('王老五')">改变Namebutton>
    
    <script>
      import { mapActions } from 'vuex';
    
      export default {
        methods: {
          // 数组的写法
          ...mapActions(["incrementAction", "changeNameAction"])
        }
      }
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 对象类型的写法
    <h2>{{ counter }}h2>
    <button @click="add">按钮button>
    <h2>{{ name }}h2>
    <button @click="changeName('罗三炮')">改变Namebutton>
    
    <script>
      import { mapActions } from 'vuex';
    
      export default {
        methods: {
          ...mapActions({
            add: "incrementAction",
            changeName: "changeNameAction"
          })
        }
      }
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    Setup中同样不推荐使用mapAction


    🍸Actions异步操作

    Action 通常是异步的,那么如何知道 action 什么时候结束呢?

    我们可以通过让action返回Promise,在Promise的then中来处理完成后的操作;

    演示代码:

    • 将网络请求的数据请求到vuex中
    const store = createStore({
      state: () => ({
        // 1.用于存放网络请求的数据
        banners: [],
        recommends: []
      }),
      mutations: {
        // 2.定义mutation用于修改state的banners和recommends
        changeBanners(state, payload) {
          state.banners = payload
        },
        changeRecommends(state, payload) {
          state.recommends = payload
        }
      },
      actions: {
        // 3.定义发生网络请求的Action
        fetchHomeMulitidataAction(context) {
          // 发生网络请求
          fetch("http://123.207.32.32:8000/home/multidata").then(res => {
            return res.json()
          }).then(data => {
            // 修改state中banner的数据, 提交mutation
            context.commit("changeBanners", data.data.banner.list)
            // 修改state中recommends的数据, 提交mutation
            context.commit("changeRecommends", data.data.recommend.list)
          })
        }
      }
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 请求到数据后可以获取vuex中的数据进行展示
    <template>
      <div class="Home">
        
        <ul>
            <li v-for="item in $store.state.banners" :key="item.acm">
              {{ item.title }}
            li>
        ul>
      div>
    template>
    
    <script setup>
      import { useStore } from 'vuex';
    
      const store = useStore()
    
      // 1.告知Action发生网络请求
      store.dispatch("fetchHomeMulitidataAction")
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    核心概念Module

    🥃module基本使用

    什么是Module?

    由于使用单一状态树,应用的所有状态会集中到一个比较大的对象,当应用变得非常复杂时,store 对象就有可能变得相当臃
    肿, 例如刚刚我们发送了一个网络请求存放Home的数据, 如果还有很多个组件需要在vuex中发送网络请求, 这个时候store对象就会越来越臃肿;

    为了解决以上问题,Vuex 允许我们将 store 分割成模块(module);

    每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块;

    演示代码

    • 将刚刚home组件请求的数据单独抽离到一个home.js文件中, js文件返回一个对象
    export default {
      state: () => ({
        // 网络请求的数据
        banners: [],
        recommends: []
      }),
      mutations: {
        // 定义mutation用于修改state的状态
        changeBanners(state, payload) {
          state.banners = payload
        },
        changeRecommends(state, payload) {
          state.recommends = payload
        }
      },
      actions: {
        fetchHomeMulitidataAction(context) {
          fetch("http://123.207.32.32:8000/home/multidata").then(res => {
            return res.json()
          }).then(data => {
            // 修改state中banner的数据, 提交mutation
            context.commit("changeBanners", data.data.banner.list)
            // 修改state中recommends的数据, 提交mutation
            context.commit("changeRecommends", data.data.recommend.list)
          })
        }
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 再将抽离出去的模块导入store对象
    // 1.导入抽离出去的模块
    import HomeModule from "../module/home"
    
    const store = createStore({
      state: () => ({}),
      getters: {},
      mutations: {},
      actions: {},
      modules: {
        //模块名: 模块对象
        home: HomeModule
      }
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 展示state状态时, 要从模块中获取; 而getter、mutation、action不需要, 因为他们默认会被合并到根模块中去, 一般情况下都可以直接获取
    
    <ul>
        
        <li v-for="item in $store.state.home.banners" :key="item.acm">
          {{item.title}}
        li>
    ul>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    对于模块内部的 mutation 和 getter,接收参数的state或getters是模块的局部状态对象

    • 还可以接收第三个参数rootState, 通过rootSate可以拿到根模块中的状态对象

    🥃module命名空间

    默认情况下,模块内部的getter、action和mutation仍然是注册在全局的命名空间中的

    这样使得多个模块能够对同一个 action 或 mutation 作出响应, 这并不是我们想要的结果, 导致在模块中我们取名需要非常小心避免重复;

    Getter 同样也默认注册在全局命名空间;

    如果我们希望模块具有更高的封装度和复用性, 解决这个尴尬的问题

    可以添加 namespaced: true 的方式使其成为带命名空间的模块

    当模块被注册后,它的所有 getter、action 及 mutation 都会自动根据模块注册的路径调整命名

    export default {
    
      // 带命名空间的模块
      namespaced: true,
    
      state: () => ({
        count: 10,
        // 网络请求的数据
        banners: [],
        recommends: []
      }),
      getters: {
        doubleCount(state) {
          return state.count * 2
        }
      },
      mutations: {
        // 定义mutation用于修改state的状态
        changeBanners(state, payload) {
          state.banners = payload
        },
        changeRecommends(state, payload) {
          state.recommends = payload
        }
      },
      actions: {
        fetchHomeMulitidataAction(context) {
          fetch("http://123.207.32.32:8000/home/multidata").then(res => {
            return res.json()
          }).then(data => {
            // 修改state中banner的数据, 提交mutation
            context.commit("changeBanners", data.data.banner.list)
            // 修改state中recommends的数据, 提交mutation
            context.commit("changeRecommends", data.data.recommend.list)
          })
        }
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 加上命名空间后, 我们需要按照如下的方式获取模块中的getter、action和mutation
    
    <h2>{{ $store.getters["home/doubleCount"] }}h2>
    
    • 1
    • 2
    // 向home中的action分发
    store.dispatch("home/fetchHomeMulitidataAction")
    
    • 1
    • 2

    module修改或派发根组件

    因此context并不等于store, 在不同模块中的context代表是不同的

    在这里插入图片描述

  • 相关阅读:
    日本政企员工喝醉丢失46万信息U盘,公开道歉又透露密码规则
    云存储架构——打造安全的企业级数据流转平台技术方案
    大数据算法系列9:字符串匹配问题,海量字符串处理
    制造企业如何做好MES管理系统需求分析
    深度学习中图像格式选用jpg还是png?答:png
    AUTOSAR知识点 之 AUTOSAR-OS (三):基础认知+任务调度博文知识点补充
    5分钟 将“.py”文件转为“.pyd”文件
    Leetcode 【1488. 避免洪水泛滥】
    假期AI新闻热点:亚运会Al技术亮点;微软GPT-4V论文精读;Perplexity推出pplx-api;DALL-E 3多渠道测评 | ShowMeAI日报
    MySQL语法笔记(自用)
  • 原文地址:https://blog.csdn.net/m0_71485750/article/details/125975829