Vue状态管理模式---Vuex
1. Vuex是做什么的?
- 官方解释: Vuex 是一个专为Vue.js 应用程序开发的 状态管理模式
- 状态管理到底是什么?
- 状态管理模式、集中式存储管理
- 简单的将其看成把需要多个组件共享的变量全部存储在一个对象里面
- 然后, 将这个对象放在顶层的Vue实例中, 让其他组件可以使用
- 那么,多个组件是不是就可以共享这个对象中的所有变量属性了呢?
1.1 管理什么状态?
- 有什么状态是我们需要在多个组件间共享的呢?
- 如果做过大型开发, 一定遇到过多个状态, 在多个界面间的共享问题
- 比如用户的登录状态、用户名称、头像、地理位置信息等
- 比如商品的收藏、购物车中的物品等等
- 这些状态信息,我们都可以放在统一的地方, 对它进行保存和管理, 并且它们还是响应式的
1.2 单页面的状态管理
-
我们知道, 要在单个组件中进行状态管理是一件非常简单的事情
- 什么意思呢? 我们来看下面的图片
-
这图片中的三种东西, 怎么理解呢?
- State: 不用多说, 就是我们的状态, 姑且可以当做就是data 中的属性
- View: 视图层, 可以针对State的变化, 显示不同的信息
- Actions: 这里的Actions 主要是用户的各种操作: 点击,输入等等, 会导致状态的改变
1.3 单页面状态管理的实现
- 在这个案例中, 我们有没有状态需要管理呢? , 没错,就是个数counter
- counter 需要某种方式被记录下来, 也就是我们的 State
- counter 目前的值需要被显示在界面中, 也就是我们的View部分
- 界面发生某些操作时 (我们这里是用户的点击, 也可以是用户的input ), 需要去更新状态, 也就是我们的 Actions
- 这就是一个基本的单页面状态管理
1.4 多页面状态管理
-
Vue 已经帮我们做好了单个界面的状态管理, 但是如果是多个界面呢?
- 多个视图都依赖同一个状态 (一个状态改了, 多个界面需要进行更新)
- 不同界面的 Actions 都想修改同一个状态 (Home,vue需要修改, Profile.vue 也需要修改这个状态)
-
也就是说对于某些状态 (状态1\状态2\状态3) 来说只属于我们某一个视图, 但是也有一些状态( 状态a\状态b\状态c ) 属于多个视图共同想要维护的
- 状态1, 状态2, 状态3 你放在自己的房间中 ,你自己管理自己用, 没问题
- 但是状态a 状态b 状态c 我们希望交给一个大管家来统一帮助我们管理
- Vuex 就是为我们提供这个大管家的工具
-
全局单例模式 (大管家)
- 我们现在要做的就是将共享的状态抽取出来, 交给我们的大管家, 统一进行管理
- 之后, 你们每个视图, 按照我 规定好的 规定, 进行访问和修改等操作
- 这就是 Vuex 背后的基本思想
1.5 Vuex 状态管理图例
1.6 简单的案例
-
首先, 我们需要在某个地方存放我们的Vuex 代码:
- 这里, 我们先创建一个文件夹 store , 并且在其中创建一个 index.js文件
- 在index.js文件中写入如下代码
-
其次, 我们让所有的Vue组件都可以使用这个
store
对象-
来到
main.js
文件, 导入store
对象, 并且放在new Vue
中 -
这样, 在其他的Vue组件中, 我们就可以通过
$store
的方式, 获取到这个store
对象了[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ifhPbJqh-1630130671735)(img/image-20210609114408524.png)]
-
-
使用Vuex 的 content
1.7 使用步骤
- 提取出一个公共的 store对象, 用于保存在多个组件中共享的状态
- 将 store 对象放置在 new Vue 对象中, 这样可以保证在所有的组件中都可以使用到
- 在其他组件中使用 store 对象中保存的状态即可
- 通过
this.$store.state.属性
的方式来访问状态 - 通过
this.$store.commit( 'mutation中方法' )
来修改状态
- 通过
- 注意事项:
- 我们通过提交
mutation
的方式, 而非直接改变store.state.count
- 这是因为Vuex可以更明确的追踪状态的变化, 所以不要直接改变
store.state.count
的值
- 我们通过提交
2. Vuex 核心概念
- Vuex有几个比较核心的概念:
- State
- Getters
- Mutation
- Action
- Module
2.1 State 单一状态树
- Vuex提出使用单一状态树, 什么是单一状态树呢?
- 英文名称是 Single Source of Truth, 也可以翻译成单一数据源
- 但是, 它是什么呢? 我们来看一个生活中的例子
- OK, 我用一个生活中的例子做一个简单的类比
- 我们知道, 在国内我们有很多的信息需要被记录, 比如上学时的个人档案, 工作后的社保记录, 公积金记录,结婚后的婚姻信息,以及其他相关的户口, 医疗, 文凭, 房产记录等等
- 这些信息被分散在很多地方进行管理, 有一天你需要办某个业务时, (比如入户某个城市), 你会发现你需要到各个对应的工作地点去打印, 盖章各种资料信息, 最后到一个地方提交证明你的信息五五
- 这种保存信息的方案, 不仅仅低效, 而且不方便管理, 以及日后的维护也是一个庞大的工程(需要大量的各个部门的人力来维护)
- 这个和我们在应用开发中比较类似:
- 如果你的状态信息是保存到多个Store对象中的, 那么之后的管理和维护等等都会变得特别困难
- 所以Vuex 也使用了单一状态树来管理应用层级的全部状态
- 单一状态树能够让我们最直接的方式找到某个状态的片段, 而且在之后的维护和调试过程中, 也可以非常方便的管理和维护
2.2 Getter 基本使用
-
有时候,我们需要从store中获取一些state变异后的状态,比如下面的Store中:
// 获取 state 平方后的值 const store = new Vuex.Store({ state: { content: 1000 } }
-
我们可以在 Store 中定义 getters
const store = new Vuex.Store({ state: { content: 1000 }, getters: { powerContent(state) { return state.content * state.content; } } })
-
在组件中调用
<template> <div id="app"> <h2>-----------app组件 getters 相关信息-------------</h2> <p>content的平方{{ $store.getters.powerContent }}</p> </div> </template> <script> import HelloVuex from "./components/HelloVuex"; export default { name: 'App', components: { HelloVuex } } </script> <style> </style>
2.2.1 Getters 作为参数和传递参数
-
如果我们已经有了一个获取所有年龄大于20岁学生列表的 getter, 那么代码可以这样来写
-
getters默认是不能传递参数的, 如果希望传递参数, 那么只能让getter 本身返回另一个函数
-
比如上面的案例中, 我们希望根据 age 获取用户信息
-
2.3 Mutation 状态更新
-
Vuex 的store 状态的更新唯一方式: 提交Mutation
-
Mutation主要包括两部分:
- 字符串的 事件类型(type)
- 一个回调函数(handler) ,该回调函数的第一个参数就是 state
-
mutation 的定义方式:
-
通过mutation更新状态
2.3.1 Mutation传递参数
-
在通过mutation 更新数据的时候, 有可能我们希望携带一些额外的参数
- 参数被称为是 mutation 的载荷(Payload)
-
Mutation中的代码:
-
组件中的代码:
-
但是如果参数不是一个呢?
- 比如我们有很多参数需要传递
- 这个时候, 我们通常会以对象的形式传递, 也就是payload是一个对象
- 这个时候可以再从对象中取出相关的信息
methods: { addCount(count){ this.$store.commit({ type:'increaseCount', count }) } }
mutations: { increaseCount(state, payload) { console.log(payload); state.content += payload.count; } }
2.3.2 Mutation 响应规则
-
Vuex 的store 中的state是响应式的, 当state中的数据发生改变时, Vue组件会自动更新
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bzlo1b3S-1630130671741)(img/属性被添加到响应式系统中.jpg)]
-
这就要求我们必须遵守一些Vuex对应的规则:
- 提前在store中初始化好所需的属性
- 当给 state中的对象添加新属性时, 使用下面的方式:
- 方式一: 使用
Vue.set(obj,'newProp',123);
- 方式二: 用新对象给旧对象重新赋值
- 方式一: 使用
- 当要删除 state中的属性时, 使用 Vue.delete
Vue.delete(state,'key')
2.3.3 Mutation 常量类型 - 概念
- 我们来考虑下面的问题:
- 在mutation中, 我们定义了很多事件类型(也就是其中的方法名称)
- 当我们的项目增大时, Vuex管理的状态越来越多, 需要更新状态的情况越来越多, 那么意味着Mutation 中的方法越来越多
- 方法过多, 使用者需要花费大量的精力去记住这些方法, 甚至是多个文件间来回切换, 查看方法名称, 甚至如果不是复制的时候, 可能还会出现写错的情况
- 如何避免上述的问题呢?
- 在各种 Flux 视线中, 一种很常见的方案就是使用常量 替代 Mutation 事件的类型
- 我们可以将这些常量放在一个单独的文件中, 方便管理以及让整个app所有的事件类型一目了然
- 具体怎么做呢?
- 我们可以创建一个文件: mutation-types.js , 并且在其中定义我们的常量
- 定义常量时, 我们可以使用ES2015中的风格, 使用一个常量来作为函数的名称
2.3.4 Mutation 常量类型 - 代码
2.3.5 Mutation 同步函数
-
通常情况下, Vuex 要求我们 Mutation中的方法必须是同步方法
- 主要的原因是当我们使用devtools调试工具时, devtools 可以帮助我们捕捉 mutation的快照
- 但是如果是异步操作, 那么devtools将不能很好的追踪这个操作什么时候会被完成
-
比如我们之前的代码, 当执行更新时, devtools 中会有如下信息
-
但是, 如果Vuex中的代码使用了异步函数
-
你会发现state中的info数据一直没有被改变, 因为它无法追踪到
2.4 Action 的基本定义
-
我们强调, 不要在Mutation中进行异步操作
- 但是某些情况, 我们确实希望在Vuex 中进行一些异步操作, 比如网络请求, 必然是异步的, 这个时候怎么处理呢?
- Action 类似于Mutation, 但是是用来代替Mutation进行异步操作的
-
Action的基本使用代码如下:
-
context 是什么?
- context 是和store 对象具有相同方法和属性的对象
- 也就是说, 我们可以通过context 去进行commit 相关的操作, 也可以获取 context, state等
- 但是注意, 这里它们并不是同一个对象, 为什么呢? 我们后面学习Modules的时候,再具体说
-
这样的代码是否多此一举呢?
- 我们定义了actions, 然后又在actions中去进行commit, 这不是脱裤放屁吗?
- 事实上并不是这样, 如果在Vuex 中有异步操作, 那么我们可以在actions 中完成了
2.4.1 Action 的分发
-
在Vue组件中, 如果我们调用action中的方法, 那么就需要使用dispatch
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
-
同样的, 也是支持传递payload
2.4.2 Action 返回的 Promise
-
前面我们学习ES6语法的时候说过, Promise经常用于异步操作
- 在Action中, 我们可以将异步操作放在一个Promise中, 并且在成功或者失败后, 调用对应的resolve或reject
-
ok , 我们来看下面的代码:
2.5 认识Modules
-
Modules 是模块的意思, 为什么在Vuex 中我们要使用模块呢?
- Vue使用单一状态树, 那么也意味着很多状态都会交给Vuex来管理
- 当应用变得非常复杂时, store对象就有可能变的相当臃肿
- 为了解决这个问题, Vuex 允许我们将store分割成模块(Module), 而每个模块拥有自己的 state、mutations、actions、getters等
-
我们按照什么样的方式来组织模块呢?
2.5.1 Module 局部状态
- 上面的代码中, 我们已经有了整体的组织结构, 下面我们来看看具体的局部模块中的代码如何书写
- 我们在moduleA中添加state、mutations、getters
- mutation 和 getters 接收的第一个参数是局部状态对象
- 注意:
- 虽然, 我们的 doubleCount 和 increment 都是定义在对象内部的
- 但是在调用的时候, 依然是通过
this.$store
来调用的
项目结构
- 当我们的Vuex 帮助我们管理过多的内容时, 好的项目结构可以让我们的代码更加清晰
2.5.1 Module 局部状态
-
上面的代码中, 我们已经有了整体的组织结构, 下面我们来看看具体的局部模块中的代码如何书写
- 我们在moduleA中添加state、mutations、getters
- mutation 和 getters 接收的第一个参数是局部状态对象
-
注意:
- 虽然, 我们的 doubleCount 和 increment 都是定义在对象内部的
- 但是在调用的时候, 依然是通过
this.$store
来调用的
项目结构
-
当我们的Vuex 帮助我们管理过多的内容时, 好的项目结构可以让我们的代码更加清晰