零基础学会 Vuex 状态管理
Vuex 是做什么的
- 官方解释:Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。
- 它采用
集中式存储管理
应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。 - Vuex 也集成到 Vue 的[官方调试工具](devtools extension),提供了诸如零配置的 time-travel 调试、状态快照导入和导出等高级调试功能。
- 状态管理到底是什么?
- 状态管理模式、集中式存储管理。
- 简单理解为需要多个组件共享的变量全部存储在一个对象里面。
- 然后将这个对象放在顶层的 Vue 实例中,让其他组件可以使用。
- 多个组件是不是就可以共享这个对象中的所有变量属性了呢?
- 如果是这样的话,为什么官方还要专门出一个 Vuex 插件呢?难道我们自己不能封装一个对象来管理吗?
- 当然可以,只是Vuejs 带来的最大的便利就是响应式。
- 如果自己封装实现一个对象能不能保证它里面所有的属性做到响应式呢?当然也可以,但是封装起来会很麻烦。
- Vuex 就是为了提供这样一个在多个组件间共享状态的插件。
管理什么状态呢?
- 有什么状态是需要在多个组件间共享的呢?
- 如果做大型开发,一定会遇到多个状态,在多个界面间的共享问题。
- 比如用户的登陆状态、用户名称、头像、地理位置信息等等。
- 比如商品的收藏、购物车中的物品等等。
- 这些状态信息,就可以放在统一的地方,对他及及进行保存和管理,而且他们还是响应式的。
单界面的状态管理
- 使用方法:
// 在目录下创建一个 store 文件夹 // 在 store 文件夹下创建 index.js 文件 import Vue from 'vue' import Vuex from 'vuex' // 1. 安装插件 Vue.use(Vuex) // 2.创建对象 const store = new Vuex.store({ }) // 3. 导出 store 独享 export default store
// main.js 文件 import store from './store' Vue.config.productionTip = false // 关闭 Vue 的默认提醒。 Vue.prototype.$store = store new Vue({ el: 'app', store, render: h => h(App) })
2.在单个组件中进行状态管理是一件非常简单的事情。
- State:状态(可以暂且当作 data 中的属性)。
- View:视图层,可以针对 State 的变化,显示不同的信息。
- Actions:主要针对用户的各种操作:点击、输入等等,会导致状态的改变。
单界面状态管理的实现
<template> <div class="text"> <div>当前计数:{{counter}}</div> <button @click="counter+=1">+1</button> <button @click="counter-=1">-1</button> </div> </template> <script> export defalt { name: 'helloworld', data() { return { counter: 0 } } } </script>
- 在这个案例中,counter 就需要状态管理。
- counter 需要某种方式被记录下来,也就是 State。
- counter 目前的值需要被显示在界面中,也就是 View 部分。
- 界面发生某些操作时(案例中是用户的点击,也可以是用户的 input),如果需要更新状态,也就是 Activens 。
多界面状态管理
- Vue 已经帮我们做好了单个界面的状态管理,但是多个界面怎么处理呢?
- 多个视图都依赖同一个状态一个状态改了,多个界面需要进行更新)。
- 不同界面的 Actions 都想修改同一个状态,(Home.vue 需要修改,Profile.vue 也需要修改这个状态)。
- 也就是说,对于某些状态(状态1/状态2/状态3)来说只属于某一个视图,但是也有一些状态(状态a/状态b/状态c)属于多个视图共同想要维护的。
- 状态1/状态2/状态3放在自己的房间中单独管理和使用是没问题的。
- 但是状态a/状态b/状态c希望交给一个大管家来统一帮助管理!!!
- 而 Vuex 就是提供这个帮助的大管家。
- 全局单例模式(大管家)
- 将共享的状态抽取出来,交给 Vuex 统一进行管理。
- 之后就可以按照规定好的规定进行访问和修改等操作。
- 这就是 Vuex 背后的基本思想。
使用 Vuex 的 counter
- 使用 Vuex 最简单的方式:
- 提取出一个公共的 store 对象,用于保存在多个组件中共享的状态。
- 将 store 对象放置在 new Vue 对象中,这样就可以保证在所有的组件中都可以使用到。
- 在其他组件中使用 store 对象中保存的状态即可。
- 通过
this.$store.state.属性
的方式来访问状态。 - 通过 `this.$store.commit('mutation中方法')来修改状态。
- 注意事项:
- 我们通过提交 mutation 的方式,而非直接改变 store.state.count 。
- 这是因为 Vuex 可以更明确的追踪状态的变化,所以不要直接改变 store.state.count 的值。
Vuex 状态管理图例
- 官方给出的图例
Vuex 的核心概念
State单一状态树
- **单一状态树:**Single Source of Truth , 也可以翻译成单一数据源。
- 如果状态信息保存到多个 Store 对象中的时候,那么之后的管理和维护等等都会变得特别困难。
- 所以 Vuex 也使用了单一状态树来管理应用层级的全部状态。
- 单一状态树能够让我们最直接的方式找到某个状态的片段,而且在之后的维护和调试过程中,也可以非常方便地管理和维护。
Getters:
Getters 基本使用
- 有时候需要从 store 中获取一些 state 变异后的状态,比如下面的 Store 中:
const store = new Vuex.Store({ state: { students: [ {id: 110, name: 'why', age: 18}, {id: 111, name: 'kobe', age: 21}, {id: 112, name: 'lucy', age: 32}, {id: 113, name: 'mike', age: 25}, ] } })
// competed computed: { getGreaterAgesCount() { return this.$store.state.students.filter.filter(age => age >= 20).length } }
// getters getters: { greaterAgesCount: state => { return state.students.filter(s => s.age >= 20).length } }
Getters 使用
getters: { more20stu:(state) { return state.students.filter(s => s.age >= 20) }, more20stuLength(state,getters) { return getters.more20stu.length } }
Getters 作为参数和传递参数
- getters 默认是不能传递参数的,如果希望传递参数,那么只能让 getters 本身返回另一个函数。
moreAgeSttu(state) { return function() { return state.students.filter(s => s.age > age) } }
Mutation:
Mutation 状态更新
- Vuex 的 store 状态的更新唯一方式:提交Mutation。
- Mutation 主要包括两部分:
- 字符串的**事件类型(type)。
- 一个回调函数(handler),该回调函数的第一个参数就是 state 。
- mutation 的定义方式:
mutations: { increment(state) { state.count++ } }
4.通过 mutation 更新
increment: function() { this.$store.commit('increment') }
Mutation 传递参数
- 在通过 mutation 更新数据的时候,有可能我们希望携带一些额外的参数。
- 参数被称为是 mutation 的载荷(Payload)。
- Mutation 中的代码:
decrement(state,n) { state.count -= n }
decrement: function() { this.$store.conmmit('decrement',2) }
3.但是如果参数不是一个呢?
- 比如有很多参数需要传递。
- 这时通常以对象的形式传递,也就是 payload 是一个对象。
- 这时可以再从对象中取出相关的信息。
changeCount(state,payload) { state.count = payload.count }
changeCount: funtion() { this.$store.conmmit('changeCount',{count: 0}) }
Mutation 提交风格
- 上面的通过 commit 进行提交是一种普通的方式。
- Vue 还提供了另外一种风格,它是一个包含 type 属性的对象。
this.$store.conmmit({ type: 'changeCount', count: 100 })
3.Mutation 中的处理方式是将整个 commit 的对象作为 payload 使用,所以代码没有改变,依然如下:
changeCount(state,payload) { state.count = payload.count }
Mutation 响应规则
- Vuex 的 store 中的 state 是响应式的,当 state 中的数据发生改变时,Vue 组件会自动更新。
- 这就要求我们必须遵守一些 Vuex 的规则:
- 提前在 store 中初始化好所需的属性。
- 当给 state 中的对象添加新的属性时,使用下面的方法:
- 方式一:使用 Vue.set(obj,'newProp',123)。
- 方式二:用新对象给旧对象重新赋值。
- 需要响应式的删除数据需要使用 Vue.delete()。
Mutation 常量类型 - 概念
- 下面有几个问题:
- 在 mutation 中定义了很多事件类型(也就是其中的方法名称)。
- 当我们的项目增大时,Vuex 管理的状态会越来越多,需要更新状态的情况越来越多,那么意味着 Mutation 中的方法越来越多。
- 方法过多,使用者需要花费大量的精力去记住这些方法,甚至是多个文件间来回切换,查看方法名称,甚至如果不是复制的时候,可能还会出现写错的情况。
- 解决方法:
- 将方法抽取成为一个常量。
// mutation-type.js 文件 export const UPDATE_INFO = 'UPDATE_INFO'
// Vuex/index.js 文件 import * as types from './mutation-types' mutation: { [types.UPDATE_INFO](state,payload) { state.info = {...state.info, 'height': payload.height} } }
// app.vue 文件 import {UPDATE_INFO} from "./store/mutation-types" methods: { updateInfo() { this.$store.commit(UPDATE_INFO,{height: 1.88}) } }
Mutation 同步函数
- 通常情况下,Vuex 要求在 Mutation 中的方法必须是同步方法。
- 主要的原因是当我们使用 devtools 时,devtools 可以捕捉 mutation 的快照。
- 但是如果是异步操作,那么 devtools 将不能很好的追踪这个操作是什么时候会被完成。
Action:
Action 的基本定义
- mutation 中不能进行异步操作。
- 但是某些情况,我们确实希望在 Vuex 中进行一些异步操作,比如网络请求。
- Action 类似于 Mutation ,但是是用来代替 Mutation 进行异步操作的。
Action 的基本使用
- Action:使用 dispatch 提交方法。
action: { // context: 上下文 aUpdataInfo(context) { setTimeout(() => { context.conmmit('updateInfo') // updateInfo 这个方法在 mutation 中已经定义过了。 }) } }
// App.vue 文件中 updateInfo() { this.$store.dispatch('aUpdataInfo') }
认识 Module
- Module 是模块的意思。
- Vuex 使用单一状态树,那么也意味着很多状态都会交给 Vuex 来管理。
- 当应用变得非常复杂时,store 对象就有可能变得相当臃肿。
- 为了解决这个问题,Vuex 允许将 store 分割成模块(Module),而每个模块有自己的 state 、mutation 、action 、getters 等。
- 组织模块的方式:
const moduleA = { state: { ... }, mutation: { ... }, action: { ... }, getters: { ... } } const moduleB = { state: { ... }, mutation: { ... }, action: { ... }, getters: { ... } } const store = new Vuex.Store({ modules: { a: moduleA, b: moduleB } }) store.state.a // -> moduleA 的状态 store.state.b // -> moduleB 的状态
3.rootstate:根状态。
Module 的局部状态
- 上面的代码中,我们已经有了整体的组织结构,那么具体的局部模块中的代码应该怎么书写呢?
- 在 ModuleA 中添加 state 、mutation 、getters 。
- mutation 和 getters 接收的第一个参数是局部状态对象.
- 注意:
- 虽然 doubleCount 和 increment 都是定义在对象内部的。
- 但是在调用的时候,依然是通过 this.$store 来直接调用的。
Actions 的写法
- actions 的写法呢?接受一个 context 参数对象。
- 局部状态通过 context.state 暴露出来,根节点状态则为 context.rootState
const moduleA = { // ... actions: { incrementIfoddOnRootSum({state,conmmit,rootState}) { if((state.count + rootState.count) % 2 === 1) { commit('increment') } } } }
2.如果 getters 中也需要使用全局的状态,可以接受更多的参数。
const moduleA = { getters: { sumWithRootCount(state,getters,rootState) { return state.count + rootState.count } } }
3.补充
- 对象的解构:
const obj = { name: 'why', age: 18, height: 1.88 address: '洛杉矶' } const {name,height,age} = obj
项目结构
- 当 Vuex 在管理过多的内容时,好的项目结构可以让代码更清晰。
// 当 Vuex 管理较多的内容时,可以将各个属性抽取出来,作为一个单独的 js 文件。 |—— index.html |—— main.js |—— api | |__ ... # 抽取出 Api 请求 |—— components | |__ App.vue | |__ ... |__ store |—— index.js # 组装模块并导出 store 的地方 |—— actions.js # 根级别的 action |—— mutations.js # 根级别的 mutation |__ modules |—— cart.js # 购物车模块 |__ products.js # 产品模块