Vuex详解

资料来源:coderwhy老师

文章目录

    • Vuex是做什么的?
    • 官方解释:Vuex是一个专为Vue.js应用程序开发的`状态管理模式`
    • 状态管理到底是什么?
    • 为啥官方要出Vuex呢?
    • 管理什么状态呢?
    • 单界面的状态管理
    • 单界面状态管理的实现
    • 多界面状态管理
    • Vuex状态管理图例
    • Vuex核心概念
      • State单一状态树
      • Getters基本使用
      • Getters作为参数和传递参数
      • Mutation状态更新
        • Mutation主要包括两部分:
        • Mutation的定义方式:
        • 通过Mutation更新:
        • Mutation传递参数
        • Mutation提交风格
        • Mutation响应规则(数据响应原理)
        • Mutation常量类型-概念
        • Mutation同步函数
    • 零碎知识片段

Vuex是做什么的?

官方解释:Vuex是一个专为Vue.js应用程序开发的状态管理模式

Vuex就是状态管理的一个工具而已。

  1. 它采用集中式存储管理应用的所有组件状态,并以相应的规则保证状态以一种可预测的方式发生变化。
  2. Vuex也集中到Vue的官方调试工具devtools extension,提供了诸如零配置的time-travel调试,状态快照导入导出等高级调试功能。

状态管理到底是什么?

  1. 状态管理模式,集中式存储管理这些名词听起来就非常高大上,让人捉摸不透。
  2. 其实,你可以简单的将其看成把需要多个组件共享的变量全部存储在一个对象里面。
    对象:大管家,用来保存变量,管理全局变量(类似Spring中的Application),Vuex:响应式。变量的值改变的时候,所有跟这个变量有关的界面都刷新(类似JSP Application Scope)。
    状态管理模式集中式存储管理 其实就是设置一个公共的全局变量。可以把理解成单例对象(单例模式),
  3. 然后,将这个对象放在顶层的Vue实例中,让其他组件可以使用。
  4. 那么,多个组件是不是就可以共享这个对象中的所有变量属性了呢?

为啥官方要出Vuex呢?

为什么官方还要专门出一个插件Vuex呢?难道我们就不能自己封装一个对象来管理吗?
假设我们自己来封装一个,示例代码如下方:

 // 所有组件继承Vue原型
  const shareObj = {
    name: 'why'
  }
  
  Vue.prototype.shareObj = shareObj

  Vue.component('cpn1', {
    this.shareObj.name; //从prototype直接拿过来
  })

  Vue.component('cpn2', {

  })

  const app = new Vue({
    el: '#app',
    data: {

    }
  })
  // 上方的做法不好,因为它不是响应式的,如果在const shareObj重新设置name的值的时候,其他地方并不会跟着改,也就是说它不是响应式的。
  // 在data修改的数据都被加入到Vue的响应式系统里边了,而我们上边那个shareObj并没有被加入到响应式系统里边。

上方的我们自己做的状态管理模式的代码并没有做到响应式。就是说我们在shareObj修改name属性的时候,其他界面的这个变量值并没有自动改变。

  1. 当然可以,只是我们要先想想VueJS带给我们最大的便利是什么呢?没错,就是响应式。
  2. 如果你自己封装实现一个对象能不能保证它里面所有的属性做到响应式呢?当然也可以,只是自己封装可能稍微麻烦一些。
  3. 不用怀疑,Vuex就是为了提供这样一个在多个组件共享状态的插件,用它就可以了。
    Vuex既能共享又能响应式,

管理什么状态呢?

一般在我们开发里面,一般什么样的东西会放在Vuex里面进行管理呢?一般什么放Vuex里面,什么放自己组件里面呢?我们并不是所有的东西都放在Vuex里面,只有那些在多个组件需要共享的,层级关系太多的时候,我们才会放进Vuex。像父子组件只有一层关系的这种,就没必要放进Vuex里面了。那我们平时喜欢将什么状态放进Vuex里面呢?或者说, 有什么状态时需要我们在多个组件间共享的呢?

  1. 如果你做过大型开放,你一定遇到过多个状态,在多个界面间的共享问题。
  2. 比如用户的登录状态,用户名称,头像,地理位置信息等等。
    典型的示例是token,token是服务器返回的令牌,在客户端请求接口API的时候,必须携带正确而有效的token,返回请求返回结果必然失败。我们可以将这个token保存在Vuex中,当多个界面用到这个token的时候,我们就可以在Vuex中直接拿到token,直接用它就可以了。
  3. 比如商品的收藏,购物车中的物品等等
    因为啊,在电商的项目中,有很多界面都有收藏按钮,当点击收藏按钮的时候,就把商品放到Vuex里面,我们在Vuex里面做一个收藏数组来记录收藏的商品就行了。在购物车里面也是,很多界面都有加入到购物车的按钮。但我们不能把所有东西都往Vuex里面塞,往Vuex里面存放。不然Vuex越来越臃肿,不方便管理,不方便维护。
  4. 这些状态信息,我们都可以放在统一的地方,对它进行保存和管理,而且它们还是响应式。

单界面的状态管理

Vuex详解_第1张图片
变量一般是用来保存状态的,在单个页面的data中相当于图中的state了,一个state可以定义多个变量,多个状态,这个state是放在View里面显示的。state的data在view展示,而Actions(如@click事件)又控制了State的data,而data又更新了View显示,如此反复循环。
我们在用CLI搭建的Vue项目时,并没有包含Vuex插件,需要我们手动安装:

cnpm install vuex --save

这个vuex是运行时所用到的插件,所以要使用--save
Vuex所在的文件夹一般叫store,中文名之仓库,意为管理很多东西,
Vuex详解_第2张图片

单界面状态管理的实现

Vuex详解_第3张图片

多界面状态管理

Vuex详解_第4张图片
全局单例模式(大管家)

Vuex状态管理图例

Vuex详解_第5张图片
上方图中,橙色、紫色、红色组成的部分是Vuex。Vue Components则是组件,组件是能够引用Vuex的State的,这个就叫引用,这个操作是单向的(即不能从组件直接修改State)。
我们在修改State的时候,不要通过Components直接修改,而是要通过Components -> Actions -> Mutations -> State这个线路修改,也可以从Components直接修改Mutations。
这个Actions用来干啥呢?Actions是用来做异步操作的,比如网络请求,Actions连接了Backend API(即后台接口,end-to-end是端到端的意思,end就是端的意思,那么前端就是frontend),我们从这个Backend API获取数据的数据的时候,可能非常耗时,故是一个异步操作。操作完成后再提交到Mutations(为了方便Devtools跟踪),而Mutations是做同步操作的,类似一个单线程的东西。Devtools是Vue开发的一个浏览器插件,用来查看State修改的记录,方便追踪错误和调试Bug。比如很多个界面(组件)都会改State,但是你怎么知道哪个组件改了呢?这个时候,我们通过Detools查看就可以知道了。所以,如果我们绕过了Mutations环节,那么Devtools就无法跟踪。
Devtools调试界面如下:
Vuex详解_第6张图片
我们至少要通过Mutations修改State中的数据,不然Devtools无法找到相应的数据。
我们修改State的东西,都是通过mutations来修改的。然后,如果有异步操作的话,是通过actions书写代码后commit到mutations的。
下方看一个简单的案例:
Vuex详解_第7张图片
下方示例的是将Vuex挂载到Vue实例中:
Vuex详解_第8张图片
下方演示的是Vuex中的一个计数器count:
Vuex详解_第9张图片

Vuex核心概念

Vuex有几个比较核心的概念:

  1. State
    State是保存状态共享的地方,State在很多地方都有,
  2. Getters
  3. Mutation
  4. Action
    用来做异步操作的地方,最后要提交到Mutation
  5. Module
    用来划分模块,根据不同的模块进行相关数据的保存

State单一状态树

  • Vuex提出使用单一状态树,什么是单一状态树呢?
    英文名称是Single Source of Truth,也可以翻译成单一数据源。
  • 但是,它是什么呢?我们来看一个生活中的例子。
    我们知道,在国内我们有很多的信息需要被记录,比如上学时的个人档案,工作后的社保记录,公积金记录,结婚后的婚姻信息,以及其他相关的户口,医疗,文凭,房产记录等等(还有很多信息)。
    这些信息被分散在很多地方进行管理,有一天你需要办某个业务时(比如入户某个城市),你会发现你需要到各个对应的工作地点去打印,盖章各种资料信息,最后到一个地方提交证明你的信息无误。
    这种保存信息的方案,不仅仅低效,而且不方便管理,以及日后的维护也是一个庞大的工作。
  • 这个和我们在应用开发中比较类似:
    如果你的状态信息是保存到多个Store对象中的,那么之后的管理和维护等等都会变得特别困难。
    所以Vuex也使用了单一状态树来管理应用层级的全部状态。
    单一状态树能够让我们最直接的方式找到某个状态的片段,而且在之后的维护和调试过程中,也可以非常方便的管理和维护。
    Vue不建议使用多个Store,因为不方便进行维护。我们只要将需要管理的信息,统一放到一个Store对象里面就可以了。
    单一状态树就是说白了就是只使用一个状态的Store,不要写一大堆的Store。
    单一状态树能够让我们最直接的方式找到状态的某个片段,比如说我们找哪个片段的话,我们肯定在一个Store里面。
    单一状态树,其实就是单一数据源。

Getters基本使用

Getters类似我们单一组件的计算属性computed。我们什么时候会用到计算属性呢?计算属性是我们一些数据需要进过一系列变化之后再展示的,就需要用到计算属性。
Vuex详解_第10张图片

Getters作为参数和传递参数

具体看代码注释吧…
还有最下方的ppt…
index.js:

import Vue from 'vue'
import Vuex from 'vuex'

import mutations from './mutations'
import actions from './actions'
import getters from './getters'
import moduleA from './modules/moduleA'

// 1.安装插件
Vue.use(Vuex) // 这个代码底层会去执行install方法

// 2.创建对象, 对应官网中https://vuex.vuejs.org/zh/ 那个图中的state
const state = {
  counter: 1000,
  students: [
    {id: 110, name: 'why', age: 18},
    {id: 111, name: 'kobe', age: 24},
    {id: 112, name: 'james', age: 30},
    {id: 113, name: 'curry', age: 10}
  ],
  info: {
    name: 'kobe',
    age: 40,
    height: 1.98
  }
}
// 1.0版本:创建对象
const store = new Vuex.Store({
  state: {
    counter: 1000
  },
  mutations: {
    // 这里面可以写很多方法
    increment(state) { //这里state是默认存在的,不需要自己去书写额外获取state的代码
      state.counter++;
    },
    decrement(state) {
      state.counter--;
    }
  },
  actions: {

  },
  getters: {

  },
  modules: {

  }
});

// 2.0版本:并不是直接const store = Vuex(); Store是一个类
const store = new Vuex.Store({
  state,
  mutations, // mutations里面可以定义很多方法
  actions,
  getters,
  modules: {
    a: moduleA
  }
})

// 3.导出store独享
export default store


// 对象的解构
const obj = {
  name: 'why',
  age: 18,
  height: 1.88,
  address: '洛杉矶'
}

const {name, height, age} = obj;

App.vue:

<h2>----------App内容: getters相关信息----------</h2>
    <h2>{{$store.getters.powerCounter}}</h2>
    <h2>{{$store.getters.more20stu}}</h2>
    <h2>{{$store.getters.more20stuLength}}</h2>
    <h2>{{$store.getters.moreAgeStu(12)}}</h2>

getters.js:

export default  {
  // state是默认参数,每一个函数都会默认带有的。
  powerCounter(state) {
    return state.counter * state.counter
  },
  more20stu(state) {
    return state.students.filter(s => s.age > 20)
  },
  // 获取年龄超过20岁的学生个数
  // 参数中的getters就是Vuex里面的getters那个属性
  more20stuLength(state, getters) {
    return getters.more20stu.length
  },
  moreAgeStu(state) {
    // 牢记
    // 返回一个函数,这里应该是闭包,这个age是外边传进来的参数
     return function (age) {
     	return state.students.filter(s => s.age > age)
     }
    return age => {
      return state.students.filter(s => s.age > age)
    }
  }
}
// 可能在state前面加上this关键字
// 解答:传参了,所以不需要加this关键字

Vuex详解_第11张图片
上方图片中,stuByID通过这里传入的id,去拿到对应的东西。上方代码中,嵌了三个箭头函数,但是在第1层的stuByID不推荐使用箭头函数,直接写原始那种函数格式就好。

Mutation状态更新

Vuex的store状态的更新唯一方式:提交Mutation

Mutation主要包括两部分:

  1. 字符串的事件类型(type)
  2. 一个回调函数(handler),该回调函数的第一个参数就是state
    举个例子:
mutations: {
    // 这里面可以写很多方法
    increment(state) { //这里state是默认存在的,不需要自己去书写额外获取state的代码
      state.counter++;
    },
    decrement(state) {
      state.counter--;
    }
  }

上方中incrementdecrement事件类型(state) { state.counter++ }(state){ state.counter-- }回调函数

Mutation的定义方式:

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

通过Mutation更新:

increment: function() {
	this.$store.commit('increment')
}

上方的increment是事件类型,而且会调用state作为第1个参数。
App.vue:

<button @click="addCount(5)">+5</button>
<button @click="addCount(10)">+10</button>

Mutations:

incrementCount(state, count) {
    state.counter += count;
}

以上是传入一个参数的情况,那么如果我们需要传入多个参数的话该怎么处理呢?
我们以提交一个学生stu为例:
index.js:

const state = {
  counter: 1000,
  students: [
    {id: 110, name: 'why', age: 18},
    {id: 111, name: 'kobe', age: 24},
    {id: 112, name: 'james', age: 30},
    {id: 113, name: 'curry', age: 10}
  ],
  info: {
    name: 'kobe',
    age: 40,
    height: 1.98
  }
}

App.vue:

    <button @click="addStudent">添加学生</button>
	methods: {
		addStudent() {
        const stu = {id: 114, name: 'alan', age: 35}
        this.$store.commit('addStudent', stu)
      }
	}

mutations:

addStudent(state, stu) {
    state.students.push(stu)
  }

Mutation传递参数

Vuex详解_第12张图片
我们在上方传递的stu对象可以说是Payload对象。

Mutation提交风格

上面的通过commit进行提交是一种普通的方式
Vue还提供了另外一种风格,它是一个包含type属性的对象
2种不同的提交风格示例代码:

addCount(count) {
        // payload: 负载
        // 1.普通的提交封装
         this.$store.commit('incrementCount', count)

        // 2.特殊的提交封装
        this.$store.commit({
          type: 'incrementCount',
          count //原本写法是count:count
        })
      }

在特殊的提交方式中,commit里面跟的是一个对象,type就是对象类型(可以说是方法名),如果是这种特殊的提交方式,那么在mutations中,commit内整个对象就变成了payload对象。用的时候,就变成了

incrementCount(state, payload) {
    // console.log(count);
    state.counter += payload.count
  }

PPT具体内容:
Vuex详解_第13张图片

Mutation响应规则(数据响应原理)

Vuex的store中的state是响应式的,当state中的数据发生改变时,Vue组件会自动更新。
这就要求我们必须遵守一些Vue对应的规则(成为响应式的要求):

  1. 提前在store中初始化好所需的属性
  2. 当给state中的对象添加新属性时,使用下面的方式:
  • 方式一:使用Vue.set(obj, ‘newProp’, 123)
  • 方式二:用心对象给旧对象重新赋值

在State里面的对象的所有属性,被会被Vue的watcher所监视(每个属性都有一个watcher),也就是动态响应,估计里面是用到了观察者模式,
如在state中,有个info对象:

info: {
	name: 'kobe', Dep -> [Watcher]
	age: 40, Dep -> [Watcher]
	height: 1.98 Dep -> [Watcher]
}

比如name会有特定的Dep,Dep是个数组,里面存放了好多个Watcher,一个Watcher对应一个有name的地方,若name有变化,就会通知Watcher进行变化,
name,age, height等等这些属性都会被加入到响应式系统中,而响应式系统会监听属性的变化,当属性发生变化时,会通知所有界面中用到该属性的地方,让界面发生刷新。
在Mutations中,先下方这种代码,是不会被加入到响应式系统中的,因为它是后来加上的(参考第1条要求)。

updateInfo(state) {
	state.info['address'] = 'Los Angeles'
}

那有什么办法使得新加入的属性具备响应式的能力呢?我们都知道js中的push和pop方法都是响应式的。

第1中方法是使用Vue.set()方法:

Vue.set(state.info, 'address', 'Los Angeles')

这个Vue.set()是个响应式方法,所以在里面设置的属性,必将加入到响应式系统中。
假设使用delete来删除属性,看看是不是响应式的。

updateInfo(state) {
	delete state.info.age
}

经过测试后,age属性是删除了,但是delete方法并不是响应式的。
下方的Vue.delete()则是响应式:

Vue.delete(state.info, 'age')

Mutation常量类型-概念

我们来考虑下面对问题:
在mutation中,我们定义了很多事件类型(也就是其中对方法名称)
当我们对项目增大时,Vuex管理对状态越来越多,需要更新状态的情况越来越多,那么意味着Mutation中的方法越来越多
方法过多,使用者需要花费大量的经历去记住这些方法,甚至是多个文件间来回切换,查看方法名称,甚至如果不是复制的时候,可能还会出现写错的情况。
我们可以创建一个专门存放Mutations常量的文件,如mutations-types.js,代码示例如下:

export const INCREMENT = 'increment' // 普通导出(即没有default)

然后在外部引用的时候,可以这样写:

import {INCREMENT} from "./mutations-types"; // 普通的导入只能写大括号
export default {
  // 方法
  [INCREMENT](state) {
    state.counter++
  }
}  

诸如此类… …
课件:
Vuex详解_第14张图片

Mutation同步函数

通常情况下,Vuex要求我们Mutation中的方法必须是同步方法。
主要原因是当我们使用detools时,Devtools可以帮助我们捕捉Mutation的快照
但是如果是异步操作,那么Devtools将不能很好的追踪这个操作什么时候会被完成

零碎知识片段

Vue三大知识点:组件、路由、状态管理

你可能感兴趣的:(Vue)