Vue | Vue.js 全家桶 Vuex状态管理

🖥️ Vue.js专栏:Vue.js 全家桶 Vuex状态管理
🧑‍💼 个人简介:一个不甘平庸的平凡人🍬

✨ 个人主页:CoderHing的个人主页

🍀 格言: ☀️ 路漫漫其修远兮,吾将上下而求索☀️

👉 你的一键三连是我更新的最大动力❤️


目录

一、认识应用状态管理

什么是状态管理

Vuex的状态管理

VueX的状态管理图解

二、Vuex的基本使用 

Vuex的安装

创建Store

组件中使用store

单一状态树(概念)

三、核心概念State

组件获取状态

在setup中使用mapState

四、核心概念Getters

getters的基本使用

getters第二个参数

getters的返回函数

mapGetters的辅助函数

五、核心概念Mutations

Mutation基本使用

Mutation携带数据

Mutation常量类型

mapMutations辅助函数

mutation重要原则

六、核心概念Actions

actions的基本使用

actions的分发操作

actions的辅助函数

Options API

actions的异步操作

七、核心概念Modules

module的基本使用

module的局部状态

module的命名空间

module修改或派发根组件


一、认识应用状态管理

什么是状态管理

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

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

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

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

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

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

复杂的状态管理

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

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

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

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

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

        多个视图依赖于同一状态

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

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

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

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

Vuex的状态管理

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

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

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

可以将组件的内部状态抽离出来,以一个全局单例的方式来管理

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

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

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

这就是Vuex背后的基本思想,它借鉴了Flux、Redux、Elm(纯函数语言,redux有借鉴它的思想)

VueX的状态管理图解

二、Vuex的基本使用 

Vuex的安装

JavaScript
npm install vuex

创建Store

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

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

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

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

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

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

        改变store中的状态的唯一途径就显示 提交(commit)mutation

        这样使得我们可以方便的跟踪每一个状态的变化,从而让我们能够通过一些工具帮助我们更好的管理应用的状态

使用步骤:

        创建Store对象;

        在app中通过插件安装

组件中使用store

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

        在模版中使用;

        在options api中使用 如 computed

        在setup中使用

单一状态树(概念)

Vuex使用单一状态数:

        用 一个对象 就包含了全部的应用层级的状态

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

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

        但状态树和模块化并不冲突.

单一状态树的优势:

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

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

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

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

三、核心概念State

组件获取状态

在前面如果觉得那种方式有点繁琐(表达式过长),那么我们可以使用计算属性:

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

                mapState的方式一:对象类型;方式二:数组类型

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

JavaScript
computed:{
            // 这样写 还是需要写三遍
            // name(){
            //     return this.$store.state.name
            // }

            // 返回的是一个一个的函数   
            //  名 称 冲 突  按数组方式映射过来 可能会导致 名字冲突
            ...mapState(["name","level","avatarURL"]),
            // 我们可以传入一个对象 自己来定义名字
            ...mapState({
                sName:state => state.name,
                sLevel:state => state.level
            })
        }

在setup中使用mapState

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

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

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

默认情况下,Vuex并没有提供非常方便的使用mapState的方式,我们进行了一个函数的封装

(封装了一个函数)

 

不使用封装函数的方法:

JavaScript
 // 2 直接对store.state进行解构
    const store = useStore()
    // 这里不是 响应式的
    // const { name,level } = store.state
    // 可以使用 toRefs来保证 他是一个响应式
    const { name,level } = toRefs(store.state)

    // 修改level 测试
    function changeLevel(){
        //  这里 违背了逻辑的  不推荐这样写
        // 正规写法为  store.commit("increment")
        store.state.level++
    }

四、核心概念Getters

getters的基本使用

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

JavaScript
const store = createStore({
    // state(){
    //     return {
    //         counter:0
    //     }
    // }
    state:()=>({
        counter:100,
        name:"xiong",
        level:99,
        avatarURL:"http://xxxx",
        users:[
            { id:1,name:"xiong",age:18 },
            { id:2,name:"kobe",age:30 },
            { id:3,name:"james",age:25 }
        ]
    }),
    getters:{
        doubleCounter(state){
            return state.counter*2
        },
        // 需求, 求users里面所有用户的age和 ,年龄和  reduce累加
        totalAge(state){
            return state.users.reduce((preValue,item)=>{
                return preValue + item.age
            },0)
        },
        // 这里有第二值 就是 getters
        msg(state,getters){
            // 在getters属性中,获取其他的getters
            return `name:${state.name} level:${state.level} usersTotalAge:${getters.totalAge}`
        }
    },

getters第二个参数

getters可以接收第二个参数

JavaScript
getters:{
        doubleCounter(state){
            return state.counter*2
        },
        // 需求, 求users里面所有用户的age和 ,年龄和  reduce累加
        totalAge(state){
            return state.users.reduce((preValue,item)=>{
                return preValue + item.age
            },0)
        },
        // 这里有第二值 就是 getters
        msg(state,getters){
            // 在getters属性中,获取其他的getters
            return `name:${state.name} level:${state.level} usersTotalAge:${getters.totalAge}`
        }
    },

getters的返回函数

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

JavaScript
getters:{
    // 获取信息
    getusersById(state){
        return function(id){
            const user = state.users.find(item => item.id === id)
            return user
        }
    }
},

==========================
<h2>id-2的朋友信息: {{ $store.getters.getusersById(2) }}</h2>

 

mapGetters的辅助函数

这里我们也可以使用mapGetters的辅助函数

JavaScript
computed:{
    ...mapGetters(["doubleCounter","totalAge","getusersById"]),
  }

 

在setup中使用

JavaScript
<script setup>
import { computed,toRefs } from 'vue';
import { mapGetters,useStore } from 'vuex'

  const store = useStore()

  // mapGetters(["msg"]) => 返回的是一个对象
  // 这样做 还是有点麻烦
  const { msg:msgFn } =  mapGetters(["msg"])
  const msg = computed(msgFn.bind({ $store: store }))

  // 最终做法:
  // const { msg } = toRefs(store.getters)
  // 3 针对某一个getters属性使用computed
  // const msg = computed(()=> store.getters.msg)

</script>

五、核心概念Mutations

Mutation基本使用

更改Vuex的store中的状态的唯一方式就是提交muattion:

JavaScript
mutations:{
    increment(state){
        state.counter++
    },
    decrement(state){
        state.counter--
    }
}

Mutation携带数据

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

JavaScript
mutations:{
    // 普通的值  payload
    changeName(state,payload){
        state.name = payload
    },
    // payload为 对象类型
    changeInfo(state,payload){
        state.level = payload.level
        state.name = payload.name
    }
}
=====================================
changeInfo(){
    this.$store.commit("changeInfo",{
      name:"xxwn",
      level:999
     })
  }

对象风格的提交方式

JavaScript
$store.commit({
    type:"addNumber",
    count:100
})

Mutation常量类型

定义常量:mutation-type.js

JavaScript
export const ADD_NUMBER = "ADD_NUMBER"

定义mutation

JavaScript
[ADD_NUMBER](state,payload){
    state.counter += payload.count
}

提交mutation

JavaScript
$store.commit({
    type: ADD_NUMBER,
    count: 100
})

mapMutations辅助函数

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

 

在setup中使用也是一样的

mutation重要原则

mutation必须是同步函数

        因为devtool工具会记录mutation的日记

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

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

六、核心概念Actions

actions的基本使用

Action类似于mutation,不同在于:

Action提交的是mutation,而不是直接变更状态

Action可以包含任意异步操作;

 

这里有一个非常重要的参数context:

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

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

为什么它不是store对象?  ==> 等说Modules再具体说

actions的分发操作

使用action => 进行action的分发:

        分别使用store上的dispatch函数;

JavaScript
add() {
    this.$store.dispatch("increment")
}

它可以携带参数:

JavaScript
add(){
    this.$store.dispatch("increment",{ count:100 })
}
// ===========================================

JavaScript
nameBtnClick(){
    this.$store.dispatch("changeNameActions","xiaoxiong")
  }
  // 传递给 下面的
// ====================================================
changeNameActions(context,payload){
    // console.log(payload); // payload就是 传递过来的值
    context.commit("changeName",payload)
}

也可以以对象的形式进行分发(了解)

JavaScript
add(){
    this.$store.dispatch({
        type:"increment",
        count:100
    })
}

actions的辅助函数

action也有对应的辅助函数:

        对象类型的写法;

        数组类型的写法

JavaScript
methods:{
    ...mapActions(["increment","decrement"]),
    ...mapActions({
        add:"increment",
        sub:"decrement"
    })
}

Options API

JavaScript
methods:{
      // actionBtnClick(){
      //   // 点击按钮 就派发
      //   this.$store.dispatch("incrementAction")
      // },
      // nameBtnClick(){
      //   this.$store.dispatch("changeNameActions","xiaoxiong")
      // }
      // Options API写法
      ...mapActions(["incrementAction","changeNameActions"])
    }

Setup写法:

JavaScript
<script setup>
  import { useStore,mapActions } from 'vuex'
  const store = useStore()

  // 在setup中使用mapActions辅助函数
  const actions = mapActions(["incrementAction","changeNameActions"])
  const newActions = {}
  Object.keys(actions).forEach(key => {
    newActions[key] = actions[key].bind({$store:store})
  })
  const { incrementAction,changeNameActions } = newActions

  // 2 使用默认的做法
  function increment(){
    store.dispatch("incrementAction")
  }
</script>

actions的异步操作

Action通常是异步的,怎么知道action什么时候结束?

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

七、核心概念Modules

module的基本使用

什么是Module?

        由于使用单一状态树,应用的所有状态会集中到一个比较大的对象,当应用变得非常复杂时,store对象就有可能变得臃肿;

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

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

 

 

module的局部状态

对于模块内部的mutation和getter,接收的第一个参数是 模块的局部状态对象

 

module的命名空间

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

        这样使得多个模块能够对同一个action或mutation作出响应

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

如果我们希望模块具有更高的封装性和复用性,可以添加namespaced:true的方式使其成为带命名空间的模块;

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

 

 

module修改或派发根组件

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

 

  • 29
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 27
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 27
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

coderHing[专注前端]

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值