Vue3中使用vuex

373 阅读7分钟

1. 理解状态管理

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

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

  • 在Vue开发中,我们使用组件化的开发方式;
  • 而在组件中我们定义data或者在setup中返回使用的数据,这些数 据我们称之为state;
  • 在模块template中我们可以使用这些数据,模块最终会被渲染成 DOM,我们称之为View;
  • 在模块中我们会产生一些行为事件,处理这些行为事件时,有可能 会修改state,这些行为事件我们称之为actions;

image.png 3、Vuex的状态管理

  1. 管理不断变化的state本身是非常困难的:
  • 状态之间相互会存在依赖,一个状态的变化会引起另一个状态的变化,View页面也有可能会引起状态的变化;
  • 当应用程序复杂时,state在什么时候,因为什么原因而发生了变化,发生了怎么样的变化,会变得非常难以控制和追踪;
  1. 因此,我们是否可以考虑将组件的内部状态抽离出来,以一个全局单例的方式来管理呢?
  • 在这种模式下,我们的组件树构成了一个巨大的 “试图View”;
  • 不管在树的哪个位置,任何组件都能获取状态或者触发行为;
  • 通过定义和隔离状态管理中的各个概念,并通过强制性的规则来维护视图和状态间的独立性,我们的代码边会变得更加结构 化和易于维护、跟踪;
  1. 这就是Vuex背后的基本思想,它借鉴了Flux、Redux、Elm(纯函数语言,redux有借鉴它的思想。
  2. vuex的状态管理逻辑

image.png 5. Vuex 使用单一状态树:

  • 用一个对象就包含了全部的应用层级的状态;
  • 采用的是SSOT,Single Source of Truth,也可以翻译成单一数据源;
  • 这也意味着,每个应用将仅仅包含一个 store 实例;
  1. 单一状态树的优势:
  • 如果你的状态信息是保存到多个Store对象中的,那么之后的管理和维护等等都会变得特别困难;
  • 所以Vuex也使用了单一状态树来管理应用层级的全部状态;
  • 单一状态树能够让我们最直接的方式找到某个状态的片段;
  • 而且在之后的维护和调试过程中,也可以非常方便的管理和维护;

2. Vuex基本使用

  1. 安装vuex
npm install vuex
  1. 创建store
  • 每一个Vuex应用的核心就是store(仓库):
  • store本质上是一个容器,它包含着你的应用中大部分的状态(state);
import { createStore } from "vuex";

const store = createStore({
    // state有两种写法
    //)写法一: state:()=>({}
    // 写法二
    state() {
        return { counter: 0, }
    },

})
export default store;
  1. app.use(store)
import { createApp } from 'vue'
import App from './App.vue'
import store from './store'

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

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

  • 第一:Vuex的状态存储是响应式的。当Vue组件从store中读取状态的时候,若store中的状态发生变化,那么相应的组件也会被更新;
  • 第二:你不能直接改变store中的状态
    • 改变store中的状态的唯一途径就显示提交 (commit) mutation;
    • 这样使得我们可以方便的跟踪每一个状态的变化,从而让我们能够通过一些工具帮助我们更好的管理应用的状态;

4.在tempate模板中使用store中state定义的数据 -> $store.state.counter,不能在script中使用。

  <template>
    <div class="home">
        <!--  $store只能在template中使用 -->
        <h2>当前计数:{{ $store.state.counter }}</h2>
        <button @click="increment">+1</button>
    </div>
</template>

3. 核心概念一State

3.1. 基本使用 $store.state.数据名

<div class="home">
        <!--  $store只能在template中使用 -->
        <h2>当前计数:{{ $store.state.counter }}</h2>
        <button @click="increment">+1</button>
    </div>

3.2. 映射使用:计算属性和解构store.state

1.options api

image.png

2. composition api
  1. 法一: image.png
  2. 法二:

image.png

3.3 mapState辅助函数

1. option api

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

image.png

  • mapState的方式一:对象类型;
  • mapState的方式二:数组类型;
<template>
    <div class="home">
        <!-- 1. 在模板中使用多个状态 -->
        <h2>name:{{ $store.state.name }}</h2>
        <h2>level:{{ $store.state.level }}</h2>
        <h2>avatarURL:{{ $store.state.avatarURL }}</h2>
        <hr>
        <!-- 2. 计算属性(映射状态:数组语法 -->
        <h2>name{{ name }}</h2>
        <h2>level:{{ level }}</h2>
        <h2>avatarURL:{{ avatarURL }}</h2>

        <!--3.计算属性(映射的状态:对象语法)  -->
        <h2>name{{ sName }}</h2>
        <h2>level:{{ sLevel }}</h2>
    </div>
</template>
<script>
import { mapState } from 'vuex';
export default ({
    data() {
        return {

        }
    },
    computed: {
        // 映射store里的属性
        // 1. 数组的写法
        ...mapState(['name', 'level', 'avatarURL']),
        // 2. 对象的写法
        ...mapState({
            sName: state => state.name,
            sLevel: state => state.level,
        })
    }
})
</script>
<script setup>




</script>
<style lang="less" scoped></style>
  • 也可以使用展开运算符和来原有的computed混合在一起;
2. compisitionAPI

在setup中使用mapState

  • 方法一: 一步步完成
<template>
    <div class="home">
        <!--4.setup计算属性(映射的状态:对象语法)  -->
        <h2>name{{ cName }}</h2>
        <h2>level:{{ cLevel }}</h2>
    </div>
</template>
<script setup>
import { mapState, useStore } from 'vuex';
import { computed } from 'vue';
//1. 一步步完成
// 映射出name函数,level函数,函数内部通过this.$store.state获取数据
const { name, level } = mapState(['name', 'level'])
const store = useStore();

// 先拿到name函数,再给函数bind绑定this
const cName = computed(name.bind({ $store: store }));
const cLevel = computed(level.bind({ $store: store }));

</script>

  1. 方法二:封装成useState
// 封装useStatehooks

import {computed} from 'vue'
import { useStore, mapState } from "vuex";
export default function useState(mapper) {

    const store = useStore();
    const stateFnsObj = mapState(mapper);//拿到的是mapState获取的函数
    const newState = {};
    Object.keys(stateFnsObj).forEach(key => {
        // 获取obj里面所有的key
        newState[key] = computed(stateFnsObj[key].bind({ $store: store }))
    })
    return newState;
}

使用如下:

<template>
    <div class="home">
         <!--4.setup计算属性,封装useState方法  -->
         <h2>useState-name{{ name }}</h2>
        <h2>level:{{ level }}</h2>   
    </div>
</template>

<script setup>
import useState from '@/hooks/userState'
// 使用useState
const { name, level } = useState(['name', 'level'])
</script>

  1. (推荐)方法三:直接解构store.state
<template>
    <div class="home">
         <!--4.setup计算属性,封装useState方法  -->
         <h2>useState-name{{ name }}</h2>
        <h2>level:{{ level }}</h2>   
    </div>
</template>

<script setup>
import { useStore } from 'vuex';
import { toRefs} from 'vue';

// 3.直接对store.state进行结构
const store = useStore();
const { name, level } = toRefs(store.state)

</script>

4. 核心概念二getter

4.1. 基本使用$store.getters.数据名

  • 直接使用

image.png

image.png 注: reduce() 方法接收一个函数作为累加器,数组中的每个值(从左到右)开始缩减,最终计算为一个值。 reduce() 可以作为一个高阶函数用于函数的 compose。

  • 引入别的getters

image.png

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

image.png

4.2. 映射使用mapGetters

1.options api

image.png

2. composition api

法一:mapGetters,使用bind绑定$store

image.png 法二:直接解构,包裹toRefs

image.png 法三:单独使用computed

const message = computed(() => store.getters.message)

5. 核心概念三Mutations

5.1. 重要原则

  1. 修改state, 必须使用mutation

5.2. 基本使用this.$store.commit("函数名")

  1. 直接使用
  • option api

image.png 2. 传入参数:payload为对象类型

image.png

5.3. 映射使用mapMutations

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

1.option api

image.png

2. compisition api

image.png

5.4. 重要原则

  1. 一条重要的原则就是要记住 mutation 必须是同步函数
  • 这是因为devtool工具会记录mutation的日记;
  • 每一条mutation被记录, devtools都需要捕捉到前一状态和后一状态的快照;
  • 但是在mutation中执行异步操作,就无法追踪到数据的变化;

6. 核心概念四Actions

6.1 action的基本使用

  1. Action类似于mutation,不同在于:
  • Action提交的是mutation,而不是直接变更状态;
  • Action可以包含任意异步操作;
  1. 这里有一个非常重要的参数context:
  • context是一个和store实例均有相同方法和属性的context对象;
  • 所以我们可以从其中获取到commit方法来提交一个mutation,或者通过 context.state 和 context.getters 来获取 state 和 getters; image.png

image.png

6.2 mapActions的辅助函数

  1. action也有对应的辅助函数:

image.png

  • 对象类型的写法;
  • 数组类型的写法; image.png

image.png

1.option API中使用
import { mapActions } from 'vuex';
export default {
    methods: {
        ...mapActions(['incrementAction', 'changeNameAnction'])
    }
}
2.compisition API使用
<script setup>
import { useStore, mapActions } from 'vuex';
const store = useStore();
const actions = mapActions(['incrementAction', 'changeNameAnction']);
const newActions = {}
Object.keys(actions).forEach(key => {
    newActions[key] = actions[key].bind({ $store: store })
})
const { incrementAction, changeNameAnction } = newActions;

</script>

使用默认函数写法:

function increment(){
    store.dispatch("incrment")
}

6.3 actions中进行异步操作

  1. 发送网络请求
  async fetchHomeMultidataAction() {
            // 1. 返回promise,给promise设置then

            fetch("http://123.207.32.32:8000/home/multidata").then(res => {
                res.json().then(data => {
                    console.log(data);
                })
            })
            //  2 .promise的链式调用
            fetch("http://123.207.32.32:8000/home/multidata").then(res => {
                return res.json()
            }).then(data => {
                console.log(data);
            })

            // 3.async,await
            const res = await fetch("http://123.207.32.32:8000/home/multidata")
            const data = await res.json()
            console.log(data);
        }

    }
  1. 数据请求的过程 image.png
  2. promise

image.png 换成resolve(data.data);

7. 核心概念五Modules

7.1. 什么是Module?

  • 由于使用单一状态树,应用的所有状态会集中到一个比较大的对象,当应用变得非常复杂时, store 对象就有可能变得相当臃肿;
  • 为了解决以上问题, Vuex 允许我们将 store 分割成模块(module) ;
  • 每个模块拥有自己的 state、 mutation、 action、 getter、甚至是嵌套子模块;

7.2. modules的基本使用

  • 使用state时,需要通过state.modulesName.XXX

image.png

7.3. 子模块getters的使用

  • 使用getters时,是直接getters.XXX image.png

7.4. 派发事件时,不需要分模块的。提交commit时,默认也是不需要更模块名称

<script setup>
import { useStore } from 'vuex';
// 告诉vuex发送网络请求
const store = useStore();
store.dispatch('fetchHomeMultidataAction')

</script>

7.5. module的命名空间

  1. 默认情况下,模块内部的action和mutation仍然是注册在全局的命名空间中的:
  • 这样使得多个模块能够对同一个 action 或 mutation 作出响应;
  • Getter 同样也默认注册在全局命名空间;
  1. 如果我们希望模块具有更高的封装度和复用性,可以添加 namespaced: true 的方式使其成为带命名空间的模块:
  • 当模块被注册后,它的所有 getter、 action 及 mutation 都会自动根据模块注册的路径调整命名

image.png 3. 加上命名空间后,获取getters方式改变

image.png

import { useStore } from 'vuex';
// 告诉vuex发送网络请求
const store = useStore();
//store.dispatch('fetchHomeMultidataAction')
//加上命名空间后
store.dispatch('home/fetchHomeMultidataAction')

</script>

7.6.module修改或派发根组件

果我们希望在action中修改root中的state,那么有如下的方式:

image.png