vue全家桶之状态管理Vuex

一、认识应用状态管理

1.什么是状态管理

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

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

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

在这里插入图片描述

2.复杂的状态管理

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

  • JavaScript需要管理的状态越来越多,越来越复杂;
  • 这些状态包括服务器返回的数据、缓存数据、用户操作产生的数据等等;
  • 也包括一些UI的状态,比如某些元素是否被选中,是否显示加载动效,当前分页;

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

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

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

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

  • 对于一些简单的状态,确实可以通过props的传递或者Provide的方式来共享状态;
  • 但是对于复杂的状态管理来说,显然单纯通过传递和共享的方式是不足以解决问题的,比如兄弟组件如何共享数据呢?

3.Vuex的状态管理

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

  • 状态之间相互会存在依赖,一个状态的变化会引起另一个状态的变化,View页面也有可能会引起状态的变化;
  • 当应用程序复杂时,state在什么时候,因为什么原因而发生了变化,发生了怎么样的变化,会变得非常难以控制和追踪;

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

  • 在这种模式下,我们的组件树构成了一个巨大的 “试图View”;
  • 不管在树的哪个位置,任何组件都能获取状态或者触发行为;
  • 通过定义和隔离状态管理中的各个概念,并通过强制性的规则来维护视图和状态间的独立性,我们的代码边会变得更加结构化和易于维护、跟踪;

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

当然,目前Vue官方也在推荐使用Pinia进行状态管理,我们后续也会进行学习。

在这里插入图片描述

vuex

二、Vuex的基本使用

1.Vuex的安装

依然我们要使用vuex,首先第一步需要安装vuex:

  • 我们这里使用的是vuex4.x;
npm install vuex

2.创建Store

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

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

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

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

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

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

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

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

使用步骤:

  • 创建Store对象;
  • 在app中通过插件安装;

store/index.js

import { createStore } from 'vuex'const store = createStore({state: () => ({// 模拟数据// counter: 100,rootCounter: 100,name: "abc",level: 100,avatarURL: "http://xxxxxx",friends: [{ id: 111, name: "why", age: 20 },{ id: 112, name: "kobe", age: 30 },{ id: 113, name: "james", age: 25 }],}),mutations: {increment(state) {state.counter++},changeName(state, payload) {state.name = payload},incrementLevel(state) {state.level++},},
})export default store

App.vue

<template><div class="app"><h2>Home当前计数: {{ $store.state.counter }}</h2><h2>Computed当前计数: {{ storeCounter }}</h2><h2>Setup当前计数: {{ counter }}</h2><button @click="increment">+1</button></div>
</template><script>export default {computed: {storeCounter() {return this.$store.state.counter}}}
</script><script setup>import { toRefs } from 'vue'import { useStore } from 'vuex'const store = useStore()const { counter } = toRefs(store.state)function increment() {// store.state.counter++store.commit("increment")}
</script><style scoped>
</style>

main.js

import { createApp } from 'vue'
import App from './App.vue'
import store from './store'createApp(App).use(store).mount('#app')

3.组件中使用store

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

  • 在模板中使用;
  • 在options api中使用,比如computed;
  • 在setup中使用;

4.单一状态树

Vuex 使用单一状态树:

  • 一个对象就包含了全部的应用层级的状态;
  • 采用的是SSOT,Single Source of Truth,也可以翻译成单一数据源

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

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

单一状态树的优势:

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

三、核心概念State

1.组件获取状态

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

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

computed: {counter() {return this.$store.state.counter}
}

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

  • mapState的方式一:对象类型;
  • mapState的方式二:数组类型;
  • 也可以使用展开运算符和来原有的computed混合在一起;
<template><div class="app"><button @click="incrementLevel">修改level</button><!-- 1.在模板中直接使用多个状态 --><h2>name: {{ $store.state.name }}</h2><h2>level: {{ $store.state.level }}</h2><h2>avatar: {{ $store.state.avatarURL }}</h2><!-- 2.计算属性(映射状态: 数组语法) --><!-- <h2>name: {{ name() }}</h2><h2>level: {{ level() }}</h2> --><!-- 3.计算属性(映射状态: 对象语法) --><!-- <h2>name: {{ sName }}</h2><h2>level: {{ sLevel }}</h2> --><!-- 4.setup计算属性(映射状态: 对象语法) --><!-- <h2>name: {{ cName }}</h2><h2>level: {{ cLevel }}</h2> --><!-- 5.setup计算属性(映射状态: 对象语法) --><h2>name: {{ name }}</h2><h2>level: {{ level }}</h2></div>
</template><script>import { mapState } from 'vuex'export default {computed: {fullname() {return "xxx"},// name() {//   return this.$store.state.name// },...mapState(["name", "level", "avatarURL"]),...mapState({sName: state => state.name,sLevel: state => state.level})}}
</script>

2.在setup中使用mapState

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

  • 通过useStore拿到store后去获取某个状态即可;
  • 但是如果我们需要使用 mapState 的功能呢?

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

useState.js

import { computed } from 'vue'
import { useStore, mapState } from 'vuex'export default function useState(mapper) {const store = useStore()const stateFnsObj = mapState(mapper)const newState = {}Object.keys(stateFnsObj).forEach(key => {newState[key] = computed(stateFnsObj[key].bind({ $store: store }))})return newState
}
<script setup>import { computed, toRefs } from 'vue'import { mapState, useStore } from 'vuex'import useState from "../hooks/useState"// 1.一步步完成// const { name, level } = mapState(["name", "level"])// const store = useStore()// const cName = computed(name.bind({ $store: store }))// const cLevel = computed(level.bind({ $store: store }))// 2.使用useState// const { name, level } = useState(["name", "level"])// 3.直接对store.state进行解构(推荐)const store = useStore()const { name, level } = toRefs(store.state)function incrementLevel() {store.state.level++}</script><style scoped>
</style>

四、核心概念Getters

1.getters的基本使用

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

<template><div class="app"><!-- <button @click="incrementLevel">修改level</button> --><h2>doubleCounter: {{ $store.getters.doubleCounter }}</h2><h2>friendsTotalAge: {{ $store.getters.totalAge }}</h2><h2>message: {{ $store.getters.message }}</h2><!-- 根据id获取某一个朋友的信息 --><h2>id-111的朋友信息: {{ $store.getters.getFriendById(111) }}</h2><h2>id-112的朋友信息: {{ $store.getters.getFriendById(112) }}</h2></div>
</template><script>export default {computed: {}}
</script>
getters: {// 1.基本使用doubleCounter(state) {return state.counter * 2},totalAge(state) {return state.friends.reduce((preValue, item) => {return preValue + item.age}, 0)},// 2.在该getters属性中, 获取其他的gettersmessage(state, getters) {return `name:${state.name} level:${state.level} friendTotalAge:${getters.totalAge}`},// 3.getters是可以返回一个函数的, 调用这个函数可以传入参数(了解)getFriendById(state) {return function(id) {const friend = state.friends.find(item => item.id === id)return friend}}},

2.getters第二个参数

getters可以接收第二个参数:

getters: {totalPrice(state, getters) {return state.price + ", " + getters.myName},myName(state) {return state.name}
}

3.getters的返回函数

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

getters: {// 3.getters是可以返回一个函数的, 调用这个函数可以传入参数(了解)getFriendById(state) {return function(id) {const friend = state.friends.find(item => item.id === id)return friend}}
}

4.mapGetters的辅助函数

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

computed: {...mapGetters(["totalPrice", "myName"]),...mapGetters({finalPrice: "totalPrice",finalName: "myName"})    
}

在setup中使用

<template><div class="app"><button @click="changeAge">修改name</button><h2>doubleCounter: {{ doubleCounter }}</h2><h2>friendsTotalAge: {{ totalAge }}</h2><h2>message: {{ message }}</h2><!-- 根据id获取某一个朋友的信息 --><h2>id-111的朋友信息: {{ getFriendById(111) }}</h2><h2>id-112的朋友信息: {{ getFriendById(112) }}</h2></div>
</template><script>import { mapGetters } from 'vuex'export default {computed: {...mapGetters(["doubleCounter", "totalAge"]),...mapGetters(["getFriendById"])}}
</script><script setup>import { computed, toRefs } from 'vue';import { mapGetters, useStore } from 'vuex'const store = useStore()// 1.使用mapGetters// const { message: messageFn } = mapGetters(["message"])// const message = computed(messageFn.bind({ $store: store }))// 2.直接解构, 并且包裹成ref// const { message } = toRefs(store.getters)// 3.针对某一个getters属性使用computedconst message = computed(() => store.getters.message)function changeAge() {store.state.name = "kobe"}</script><style scoped>
</style>

五、核心概念Mutations

1.Mutation基本使用

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

mutations: {increment(state){state.counter++}
}

2.Mutation携带数据

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

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

payload为对象类型

increment(state, payload){state.counter += payload.count
}

对象风格的提交方式

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

3.Mutation常量类型

定义常量:mutation-type.js

export const ADD_NUMBER = 'ADD_NUMBER'

定义mutation

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

提交mutation

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

4.mapMutations辅助函数

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

methods: {...mapMutations({addNumber: ADD_NUMBER,})...mapMutations(['increment', 'decrement'])
}

5.mutation重要原则

一条重要的原则就是要记住 mutation 必须是同步函数

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

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

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

六、核心概念Actions

1.actions的基本使用

Action类似于mutation,不同在于:

  • Action提交的是mutation,而不是直接变更状态;
  • Action可以包含任意异步操作;

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

  • context是一个和store实例均有相同方法和属性的context对象;
  • 所以我们可以从其中获取到commit方法来提交一个mutation,或者通过 context.state 和 context.getters 来获取 state 和 getters;

但是为什么它不是store对象呢?这个等到我们讲Modules时再具体来说;

mutations: {increment(state) {state.counter++}
},
actions: {increment(context) {context.commit('increment')}
}    

2.actions的分发操作

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

  • 分发使用的是 store 上的dispatch函数;
add() {this.$store.dispatch('increment')
}

同样的,它也可以携带我们的参数:

add() {this.$store.dispatch('increment', { counter: 100 })
}

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

add() {this.$store.dispatch({type: 'increment',counter: 100})
}

3.actions的辅助函数

action也有对应的辅助函数:

  • 对象类型的写法;
  • 数组类型的写法;
methods: {...mapActions(['increment', 'decrement']),...mapActions({add: 'increment',sub: 'decrement'})    
}

4.actions的异步操作

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

  • 我们可以通过让action返回Promise,在Promise的then中来处理完成后的操作;
actions: {increment(context) {return new Promise((resolve) => {setTimeout(() => {context.commit('increment')resolve('一步完成')}, 1000)})}
}
const store = useStore()
const increment = () => {store.dispatch('increment').then(res => {console.log(res, '异步完成')})
}

七、核心概念Modules

1.module的基本使用

什么是Module?

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

2.module的局部状态

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

mutations: {changeName(state) {state.name = '111'}
},
getters: {info(state, getters, rootState) {}
},
actions: {changeNameAction({state, commit, rootState}) {}
}    

3.module的命名空间

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

  • 这样使得多个模块能够对同一个 action 或 mutation 作出响应;
  • Getter 同样也默认注册在全局命名空间;

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

  • 当模块被注册后,它的所有 getter、action 及 mutation 都会自动根据模块注册的路径调整命名;
const moduleA = {namespaced: true,// 访问:$store.state.模块名.xxxstate() {return {name: 'abc',age: 18}},mutations: {// 访问: $store.commit('模块名/changeName')changeName(state) {state.name = 'zhangsan'}},getters: {// 访问:$store.getters['模块名/info']// 这里又4个参数info(state, getters, rootState, rootGetters) {}},actions: {// 这里有六个参数changeNameAction({commit, dispatch, state, rootState, getters, rootGetters}) {commit('changeName', 'kobe')}}
}

4.module修改或派发根组件

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

changeNameAction({commit, dispatch, state, rootState, getters, rootGetters}) {commit('changeName', 'kobe')commit('changeRootName', null, {root: true}),dispatch('changeRootNameAction', null, {root: true})    
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/250327.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

HiSilicon352 android9.0 开机视频调试分析

一&#xff0c;开机视频概念 开机广告是在系统开机后实现播放视频功能。 海思Android解决方案在原生Android基础上&#xff0c;增加了开机视频模块&#xff0c;可在开机过程中播放视频文件&#xff0c;使用户更好的体验系统开机过程。 二&#xff0c;模块结构 1. 海思自研开机…

【Docker篇】Linux安装Docker、docker安装mysql、redis、rabbitmq

1.Linux安装docker 官方帮助文档&#xff1a;Install Docker Engine on CentOS | Docker Docs 1.1安装命令 # 1. 卸载之前的dockersudo yum remove docker \docker-client \docker-client-latest \docker-common \docker-latest \docker-latest-logrotate \docker-logrotate…

认识 SYN Flood 攻击

文章目录 1.什么是 SYN Flood 攻击&#xff1f;2.半连接与全连接队列3.如何防范 SYN Flood 攻击&#xff1f;增大半连接队列开启 SYN Cookie减少 SYNACK 重传次数 参考文献 1.什么是 SYN Flood 攻击&#xff1f; SYN Flood 是互联网上最原始、最经典的 DDoS&#xff08;Distri…

dvwa,xss反射型lowmedium

xss&#xff0c;反射型&#xff0c;low&&medium low发现xss本地搭建实操 medium作为初学者的我第一次接触比较浅的绕过思路high low 发现xss 本关无过滤 <script>alert(/xss/)</script> //或 <script>confirm(/xss/)</script> //或 <scr…

2024数学建模美赛F题思路代码分享

非法的野生动物贸易会对我们的环境产生负面影响&#xff0c;并威胁到全球的生物多样性。据估 计&#xff0c;它每年涉及高达265亿美元&#xff0c;被认为是全球第四大非法交易。[1]你将开发一个由数 据驱动的5年项目&#xff0c;旨在显著减少非法野生动物贸易。你的目标是说服一…

【24美赛思路已出】2024年美赛A~F题解题思路已出 | 无偿自提

A题&#xff1a;资源可用性和性别比例 问题一&#xff1a; 涉及当灯鱼种群的性别比例发生变化时&#xff0c;对更大的生态系统产生的影响。为了分析这个问题&#xff0c;可以采用以下的数学建模思路&#xff1a;建立灯鱼种群模型&#xff1a; 首先&#xff0c;建立一个灯鱼种群…

C语言第十六弹---操作符(下)

✨个人主页&#xff1a; 熬夜学编程的小林 &#x1f497;系列专栏&#xff1a; 【C语言详解】 【数据结构详解】 操作符 1、下标访问[]、函数调用() 1.1、[ ] 下标引用操作符 1.2、函数调用操作符 2、结构成员访问操作符 2.1、结构体 2.1.1、结构的声明 2.1.2、结构体变…

计算机网络第4章(网络层)

4.1、网络层概述 简介 网络层的主要任务是实现网络互连&#xff0c;进而实现数据包在各网络之间的传输 这些异构型网络N1~N7如果只是需要各自内部通信&#xff0c;他们只要实现各自的物理层和数据链路层即可 但是如果要将这些异构型网络互连起来&#xff0c;形成一个更大的互…

【Python笔记-设计模式】建造者模式

一、说明 又称生成器&#xff0c;是一种创建型设计模式&#xff0c;使其能够分步骤创建复杂对象。允许使用相同的创建代码生成不同类型和形式的对象。 (一) 解决问题 对象的创建问题&#xff1a;当一个对象的构建过程复杂&#xff0c;且部分构建过程相互独立时&#xff0c;可…

基于SpringBoot Vue单位考勤管理系统

大家好✌&#xff01;我是Dwzun。很高兴你能来阅读我&#xff0c;我会陆续更新Java后端、前端、数据库、项目案例等相关知识点总结&#xff0c;还为大家分享优质的实战项目&#xff0c;本人在Java项目开发领域有多年的经验&#xff0c;陆续会更新更多优质的Java实战项目&#x…

C#——三角形面积公式

已知三角形的三个边&#xff0c;求面积&#xff0c;可以使用海伦公式。 因此&#xff0c;可以执行得到三角形面积公式的计算方法代码如下&#xff1a; /** / <summary>* / 三角形面积公式* / </summary>* / <param name"a">边长a</param>*…

Android矩阵Matrix裁切setRectToRect拉伸Bitmap替代Bitmap.createScaledBitmap缩放,Kotlin

Android矩阵Matrix裁切setRectToRect拉伸Bitmap替代Bitmap.createScaledBitmap缩放&#xff0c;Kotlin class MyImageView : AppCompatImageView {private var mSrcBmp: Bitmap? nullprivate var testIV: ImageView? nullconstructor(ctx: Context, attrs: AttributeSet) :…

VUE3+TS使用OpenSeadragon学习之旅,实现多图片切换效果

1.官方网站&#xff1a;OpenSeadragon 2.使用npm下载插件&#xff1a;npm install openseadragon 3.在 index.html文件引入资源 <link rel"stylesheet" href"node_modules/openseadragon/build/openseadragon/openseadragon.css" /><script src…

Python GCN、GAT、MP等图神经网络学习,从入门全面概述和讲解GNN,入门到精通图神经网络

1. 图的分类&#xff1a; 1.1 根据边的方向性&#xff1a; 有向图&#xff08;Directed Graph&#xff09;&#xff1a;图中的边具有方向性&#xff0c;表示节点之间的单向关系。例如&#xff0c;A指向B的边表示节点A指向节点B。无向图&#xff08;Undirected Graph&a…

LVGL部件7

一.图片部件 1.知识概览 2.函数接口 1.lv_img_set_pivot 在LVGL&#xff08;LittlevGL&#xff09;中&#xff0c;要设置图像对象的旋转中心点&#xff0c;可以使用 lv_img_set_pivot 函数。该函数的原型如下&#xff1a; void lv_img_set_pivot(lv_obj_t * img, lv_coord_…

idea项目如何上传gitee

1.先创建仓库&#xff08;nonono&#xff01;&#xff01;&#xff01;idea上传会自动创建仓库&#xff01;&#xff01;&#xff01;&#xff01;&#xff09; 2.从gitee上面clone下来&#xff08;nonono&#xff01;&#xff01;&#xff01;&#xff01;这个.git文件也是自动…

6-TAMRA NH2,四甲基罗丹明氨基,常用于生物标记和荧光检测实验

您好&#xff0c;欢迎来到新研之家 文章关键词&#xff1a;6-TAMRA amine&#xff0c;6-TAMRA NH2&#xff0c;四甲基罗丹明氨基 一、基本信息 产品简介&#xff1a;6-TAMRA amine is widely used in various biomarker and fluorescence detection experiments. 6-TAMRA am…

前端通过nginx,访问一个文件夹里面的全部数据,nginx 咋配置

目录 1 问题2 实现 1 问题 前端通过nginx,访问一个文件夹里面的全部数据&#xff0c;nginx 咋配置 2 实现 location /logs {alias /mnt/www/logs/;autoindex on; }

小程序中picker多列选择器

需求&#xff1a;实现类似省市联动的效果&#xff0c;选择第一列后&#xff0c;第二列数据变化 html部分: <view class"section"><view>多列选择器</view><picker mode"multiSelector" bindchange"bindMultiPickerChange"…

tcpdump在手机上的使用

首先手机得root才可以&#xff0c;主要分析手机与手机的通信协议 我使用的是一加9pro&#xff0c; root方法参考一加全能盒子、一加全能工具箱官方网站——大侠阿木 (daxiaamu.com)https://optool.daxiaamu.com/index.php tcpdump&#xff0c;要安装在/data/local/tmp下要arm6…