1.Vuex是做什么的?
官方解释:Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。
- 它采用 集中式存储管理 应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
- Vuex 也集成到 Vue 的官方调试工具 devtools extension,提供了诸如零配置的 time-travel
调试、状态快照导入导出等高级调试功能。
状态管理到底是什么?
- 可以简单的将其看成把需要多个组件共享的变量全部存储在一个对象里面。
- 然后,将这个对象放在顶层的Vue实例中,让其他组件可以使用。
- 那么,多个组件是不是就可以共享这个对象中的所有变量属性了呢?
等等,如果是这样的话,为什么官方还要专门出一个插件Vuex呢?难道我们不能自己封装一个对象来管理吗?
- 当然可以,只是我们要先想想VueJS带给我们最大的便利是什么呢?没错,就是响应式。
- List item
- 如果你自己封装实现一个对象能不能保证它里面所有的属性做到响应式呢?当然也可以,只是自己封装可能稍微麻烦一些。
- 不用怀疑,Vuex就是为了提供这样一个在多个组件间共享状态的插件,用它就可以了。
2.管理什么状态呢?
有什么状态时需要我们在多个组件间共享的呢?
- 比如用户的登录状态、用户名称、头像、地理位置信息等等。
- 比如商品的收藏、购物车中的物品等等。
- 这些状态信息,我们都可以放在统一的地方,对它进行保存和管理,而且它们还是响应式的。
3.单界面的状态管理
要在单个组件中进行状态管理是一件非常简单的事情
这图片中的三种东西,怎么理解呢?
State:不用多说,就是我们的状态。(你姑且可以当做就是data中的属性)
View:视图层,可以针对State的变化,显示不同的信息。
Actions:这里的Actions主要是用户的各种操作:点击、输入等等,会导致状态的改变。
4.多界面状态管理
Vue已经帮我们做好了单个界面的状态管理,但是如果是多个界面呢?
- 多个试图都依赖同一个状态(一个状态改了,多个界面需要进行更新)
- 不同界面的Actions都想修改同一个状态(Home.vue需要修改,Profile.vue也需要修改这个状态)
也就是说对于某些状态(状态1/状态2/状态3)来说只属于我们某一个视图,但是也有一些状态(状态a/状态b/状态c)属于多个试图共同想要维护的
- 状态1/状态2/状态3你放在自己的房间中,你自己管理自己用,没问题。
- 但是状态a/状态b/状态c我们希望交给一个大管家来统一帮助我们管理!!!
- Vuex就是为我们提供这个大管家的工具。
全局单例模式(大管家)
- 我们现在要做的就是将共享的状态抽取出来,交给我们的大管家,统一进行管理。
- 之后,你们每个试图,按照我规定好的规定,进行访问和修改等操作。 这就是Vuex背后的基本思想。
用cli2建一个项目
npm install vuex --save
新建文件夹
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中导入
所有的vue组件都有store对象了
import Vue from 'vue'
import App from './App'
import store from "./store";
Vue.config.productionTip = false
/* eslint-disable no-new */
new Vue({
el: '#app',
store,
render: h => h(App)
})
stroe/index.js
创建对象中添加要共享的状态state
import Vue from 'vue'
import Vuex from 'vuex'
//1.安装插件
Vue.use(Vuex)
//2.创建对象
const store = new Vuex.Store({
//保存状态
state:{
counter:100
}
})
//3.导出store独享
export default store
在App.vue中应用时,注意要注销对counter的初始化
但是这里的click并不是按规定修改的,下面我们会进一步讲解
<template>
<div id="app">
<p>__________App_______</p>
<h2>{{message}}</h2>
<p>{{$store.state.counter}}</p>
<button @click="$store.state.counter++">+</button>
<button @click="$store.state.counter--">-</button>
<p>_____Hellovuex______</p>
<hellovuex counter="counter"/>
</div>
</template>
<script>
import Hellovuex from "./components/Hellovuex";
export default {
name: 'App',
data(){
return{
message:'我是组件',
// counter:0
}
},
components: {
Hellovuex
}
}
</script>
在Hellovuex.vue中使用时也是
<template>
<div>
<h2>{{$store.state.counter}}</h2>
</div>
</template>
<script>
export default {
name: "Hellovuex",
props:{
// counter:Number
}
}
</script>
效果如图
以上操作是直接对state进行修改的
接下来我们用mutations来实现修改
- 首先,我们需要在某个地方存放我们的Vuex代码:
这里,我们先创建一个文件夹store,并且在其中创建一个index.js文件
在index.js文件中写入如下代码:
store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
//1.安装插件
Vue.use(Vuex)
//2.创建对象
const store = new Vuex.Store({
//保存状态
state:{
counter:100
},
mutations:{
//方法
increment(state){
state.counter++
},
decrement(state){
state.counter--
}
}
})
//3.导出store独享
export default store
- 其次,我们让所有的Vue组件都可以使用这个store对象
来到main.js文件,导入store对象,并且放在new Vue中
这样,在其他Vue组件中,我们就可以通过this.$store的方式,获取到这个store对象了
import Vue from 'vue'
import App from './App'
import store from "./store";
Vue.config.productionTip = false
/* eslint-disable no-new */
new Vue({
el: '#app',
//将store对象放置在new Vue对象中,这样可以保证在所有的组件中都可以使用到
store,
render: h => h(App)
})
在其他组件中使用store对象中保存的状态即可
通过this.$store.state
.属性的方式来访问状态
通过this.$store.commit('mutation中方法')
来修改状态
5.Vuex核心概念
Vuex有几个比较核心的概念:
5.1State单一状态树
Vuex提出使用单一状态树, 什么是单一状态树呢?
- 英文名称是Single Source of Truth,也可以翻译成单一数据源。
但是,它是什么呢?我们来看一个生活中的例子。
- 我们知道,在国内我们有很多的信息需要被记录,比如上学时的个人档案,工作后的社保记录,公积金记录,结婚后的婚姻信息,以及其他相关的户口、医疗、文凭、房产记录等等(还有很多信息)。
- 这些信息被分散在很多地方进行管理,有一天你需要办某个业务时(比如入户某个城市),你会发现你需要到各个对应的工作地点去打印、盖章各种资料信息,最后到一个地方提交证明你的信息无误。
- 这种保存信息的方案,不仅仅低效,而且不方便管理,以及日后的维护也是一个庞大的工作(需要大量的各个部门的人力来维护,当然国家目前已经在完善我们的这个系统了)。
这个和我们在应用开发中比较类似:
- 如果你的状态信息是保存到多个Store对象中的,那么之后的管理和维护等等都会变得特别困难。
- 所以Vuex也使用了单一状态树来管理应用层级的全部状态。
- 单一状态树能够让我们最直接的方式找到某个状态的片段,而且在之后的维护和调试过程中,也可以非常方便的管理和维护,一个项目终止创建一个store就可以了(我们这个store文件夹下的index,js中就是)
5.2Getters基本使用
Getters-计算属性
有时候,我们需要从store中获取一些state变异后的状态,比如下面的Store中:
案例一——输出平方
案例二——获取学生年龄大于20的个数。
App.vue
5.3Getters作为参数和传递参数
案例一——想要获取所有年龄大于20岁学生的个数
App.vue中添加
<p>大于20岁的人员个数为:{{$store.getters.ageCounterlength}}</p>
index.js的getters中添加
ageCounterlength(state){
return state.peoples.filter(s => s.age>20).length
}
我们还可以这样写,传递两个参数
ageCounterlength(state,getters){
return getters.ageCounter.length
}
案例二——想获取年龄大于age的,age是后给定的值
getters默认是不能传递参数的, 如果希望传递参数, 那么只能让getters本身返回另一个函数.
index.js的getters中添加
ageCounter2(state) {
// return function (age) {
// return state.peoples.filter(s => s.age>age)
// }
return age => {
return state.peoples.filter(s => s.age>age)
}
},
App.vue中添加
<p>大于指定年龄人员有:{{$store.getters.ageCounter2(6)}}</p>
5.4Mutations状态更新
Vuex的store状态的更新唯一方式:提交Mutation
Mutation主要包括两部分:
- 字符串的事件类型(type)
- 一个回调函数(handler),该回调函数的第一个参数就是state。
mutation的定义方式:
通过mutation更新
5.4.1Mutations传递参数
在通过mutation更新数据的时候, 有可能我们希望携带一些额外的参数
案例一实现一次加5或加10
Mutation中的代码:
incrementCount(state,count) {
state.counter += count
}
App.vue中代码
<button @click="addcount(5)">+5</button>
<button @click="addcount(10)">+10</button>
methods:{
addcount(count){
this.$store.commit('incrementCount',count)
}
}
案例二,点击按钮给people是数组中添加成员
- 参数被称为是mutation的载荷(Payload)
- 下面的参数peo就是mutation的载荷
App.vue
<button @click="addpeople">增加一个学生</button>
...此处省略n行代码
methods:{
addpeople(){
const peo = {name:'丽丽',age:82}
this.$store.commit('incrementpeople',peo)
}
}
Mutation中的代码:
incrementpeople(state,peo) {
state.peoples.push(peo)
}
5.4.2Mutations提交风格
上面的通过commit进行提交是一种普通的方式
Vue还提供了另外一种风格, 它是一个包含type属性的对象
普通提交风格
App.vue
addcount(count){
this.$store.commit('incrementCount',count)
},
incrementCount(state,count) {
console.log(count)
state.counter += count
},
特殊提交风格
addcount(count){
this.$store.commit({
type:'incrementCount',
count,
})
},
incrementCount(state,payload) {
console.log(count)
state.counter += payload.count
},
两种提交风格,提交的参数是不一样类型的
5.4.3Mutations响应规则
Vuex的store中的state是响应式的, 当state中的数据发生改变时, Vue组件会自动更新.
这就要求我们必须遵守一些Vuex对应的规则:
- 提前在store中初始化好所需的属性.
修改属性
index.js
state:{
info: {
name:'smy',
age:40,
height:1.98
}
},
mutations:{
gaiming(state) {
state.info.name = 'yyqx'
}
},
App.vue
<p>__________App内容info对象的内容是否是响应式___________</p>
<p>{{$store.state.info}}</p>
<button @click="changename">改名</button>
changename(){
this.$store.commit('gaiming')
}
点击按钮姓名发生修改
在state中,这些属性都会被加入到响应式系统中,响应式系统会监听属性的变化,当属性发生变化时,会通知界面中所有用到该属性的地方,让界面发生刷新
给对象添加新属性时
state.info.name = 'yyqx'
修改为
state.info['address'] = '北京'
因为address不是store中初始化好的属性,我们点击按钮,后天添加的这个是不会添加到响应式系统中的,不能实现响应式(注意这里是基于cli2手脚架的,3/4会自动刷新)
那么我们怎么让set变成响应式的呢?
set(要修改的对象,索引值,修改后的值)
我们把
state.info['address'] = '北京'
改为
Vue.set(state.info,‘address’,‘北京’)
注意类型(分数组和对象)不同,传的索引值不一样
删除指定元素,要想是响应式的
Vue.delete(state.info,'age')
5.4.4Mutations常量类型 – 概念
我们来考虑下面的问题:
- 在mutation中, 我们定义了很多事件类型(也就是其中的方法名称).
- 当我们的项目增大时, Vuex管理的状态越来越多, 需要更新状态的情况越来越多, 那么意味着Mutation中的方法越来越多.
- 方法过多, 使用者需要花费大量的经历去记住这些方法, 甚至是多个文件间来回切换, 查看方法名称, 甚至如果不是复制的时候,
可能还会出现写错的情况.
如何避免上述的问题呢?
- 在各种Flux实现中, 一种很常见的方案就是使用常量替代Mutation事件的类型.
- 我们可以将这些常量放在一个单独的文件中, 方便管理以及让整个app所有的事件类型一目了然.
具体怎么做呢?
- 我们可以创建一个文件: mutation-types.js, 并且在其中定义我们的常量.
export const INCREMENT = 'increment'
之后修改App.vue
methods:{
addition(){
this.$store.commit(INCREMENT)//提交刚才定义的方法名字
},
}
index.js
mutations:{
//方法
[INCREMENT](state){
state.counter++
},
}
5.4.5Mutations同步函数
通常情况下, Vuex要求我们Mutation中的方法必须是同步方法.
- 主要的原因是当我们使用devtools时, 可以devtools可以帮助我们捕捉mutation的快照.
- 但是如果是异步操作, 那么devtools将不能很好的追踪这个操作什么时候会被完成.
gaiming(state) {
setTimeout(() =>{
state.info.name = 'yyqx'
},1000)
}
点击按钮页面上name改变但是devtools里的name不改变
同步操作
gaiming(state) {
state.info.name = 'yyqx'
}
So, 通常情况下, 不要再mutation中进行异步的操作
5.5Actions-做一些异步操作
我们强调, 不要再Mutation中进行异步操作.
- 但是某些情况, 我们确实希望在Vuex中进行一些异步操作, 比如网络请求, 必然是异步的. 这个时候怎么处理呢?
- Actions类似于Mutation, 但是是用来代替Mutation进行异步操作的.
Actions的基本使用代码如下:
mutations:{
gaiming(state) {
state.info.name = 'yyqx'
}
},
actions:{
aUpdateInfo(context) {
setTimeout(() =>{
context.commit('gaiming')
})
}
},
如果我们调用action中的方法, 那么就需要使用dispatch
changename(){
this.$store.dispatch('aUpdateInfo')
}
context是什么?
- context是和store对象具有相同方法和属性的对象.
- 也就是说, 我们可以通过context去进行commit相关的操作, 也可以获取context.state等.
这样的代码是否多此一举呢? - 事实上并不是这样, 如果在Vuex中有异步操作, 那么我们就可以在actions中完成了.
actions中也是支持传递payload
mutations:{
gaiming(state) {
state.info.name = 'yyqx'
}
},
actions:{
aUpdateInfo(context,payload) {
setTimeout(() =>{
context.commit('gaiming')
console.log(payload);
})
}
},
changename(){
this.$store.dispatch('aUpdateInfo','我是payload')
}
修改成功想作出提示
mutations:{
gaiming(state) {
state.info.name = 'yyqx'
}
},
actions:{
aUpdateInfo(context,payload) {
setTimeout(() =>{
context.commit('gaiming')
console.log(payload.message);
payload.success()
},100)
}
},
changename(){
this.$store.dispatch('aUpdateInfo',{
message:'我是携带的信息',
success: () => {
console.log('里面已经完成了')
}
})
}
前面我们学习ES6语法的时候说过, Promise经常用于异步操作.
在Action中, 我们可以将异步操作放在一个Promise中, 并且在成功或者失败后, 调用对应的resolve或reject.
actions:{
aUpdateInfo(context,payload) {
return new Promise((resolve, reject) =>{
setTimeout(() =>{
context.commit('gaiming')
console.log(payload);
resolve('111111')
},100)
})
}
},
changename(){
this.$store
.dispatch('aUpdateInfo','我是携带的信息')
.then(res => {
console.log('里面完成了提交');
console.log(res);
})
}
5.6Module-划分模块,再做数据保存
Module是模块的意思, 为什么在Vuex中我们要使用模块呢?
- Vue使用单一状态树,那么也意味着很多状态都会交给Vuex来管理.
- 当应用变得非常复杂时,store对象就有可能变得相当臃肿.
- 为了解决这个问题, Vuex允许我们将store分割成模块(Module),
而每个模块拥有自己的state、mutations、actions、getters等
我们按照什么样的方式来组织模块呢?
模块中state怎么用的呢?
所以我们这样用,获取moduleA中的name
<p>_________modules中的内容_________</p>
<p>{{$store.state.a.name}}</p>
模块中的mutations怎么用呢?
<p>_________modules中的内容_________</p>
<p>{{$store.state.a.name}}</p>
<button @click="changename2">改名</button>
注意:
虽然, 我们的gaiming1都是定义在对象内部的.
但是在调用的时候, 依然是通过this.$store来直接调用的.
changename2(){
this.$store.commit('gaiming1','Lisa')
}
点击按钮
基本上和正常使用mutations一样,事件名不要重复,不然是优先store中的mutations
模块中的getters怎么使用呢?
<p>{{$store.getters.fullname}}</p>
<p>{{$store.getters.fullname2}}</p>
<p>{{$store.getters.fullname3}}</p>
模块中的actions怎么使用呢?
const moduleA = {
mutations:{
gaiming1(state,payload){
state.name = payload
}
},
actions:{
aUpdateName2(context){
setTimeout(() =>{
context.commit('gaiming1','who?')
},1000)
}
},
}
<button @click="asyncUpdateName">异步修改名字</button>
asyncUpdateName(){
this.$store.dispatch('aUpdateName2')
}
点击按钮前
点击按钮后
也可以通过对象的结构将context参数对象改为state,commit,rootState
局部状态通过 context.state 暴露出来
根节点状态则为 context.rootState
//对象的解构
const obj = {
name:'smy',
age:18,
height: 1.88
}
const {name,height,age} = obj;
store目录结构
在store文件夹下新建js文件,modules单建一个文件夹放模块文件
index.js
import Vue from 'vue'
import Vuex from 'vuex'
import getters from "./getters";
import mutations from "./mutations";
import actions from "./actions";
import moduleA from "./modeles/moduleA";
//1.安装插件
Vue.use(Vuex)
const state ={
counter:100,
peoples:[
{name:'mm',age:19},
{name:'ss',age:49},
{name:'aa',age:21},
{name:'yy',age:12},
{name:'ee',age:8},
],
info: {
name:'smy',
age:40,
height:1.98
}
}
//2.创建对象
const store = new Vuex.Store({
state,
mutations,
actions,
getters,
modules:{
a: moduleA
}
})
//3.导出store独享
export default store
以getters.js为例,其他的一样就是把之前的内容扔到这里
export default {
powerCounter(state) {
return state.counter*state.counter
},
ageCounter(state) {...代码省略},
ageCounterlength(state,getters) {...},
ageCounter2(state) {...},
}
(以上内容根据微博“coderwhy”的vue视频课程整理,感谢王红元老师ღ( ´・ᴗ・` )比心)