Vuex小结

140 阅读6分钟

学习记录

今天自己学习了半天的Vuex,然后在这里做一个小结。
自己之前对于Vuex了解只是知道有这个玩意儿,知道它是一个状态管理模式。其他的是一概不知(丢脸)。
然后开始看了挺久的官网,看的迷迷糊糊的,是我太菜了,哈哈哈。
然后选择了去看网课,看了codewhy老师的网课,讲的挺好的,看了挺长时间的
然后通过今天的学习,大致了解如下:

概述

状态管理模式、集中式存储管理这些名词听起来就非常高大上,让人捉摸不透。

  • 其实,你可以简单的将其看成把需要多个组件共享的变量全部存储在一个对象里面。
  • 然后,将这个对象放在顶层的Vue实例中,让其他组件可以使用。
  • 那么,多个组件是不是就可以共享这个对象中的所有变量属性了呢?
  • 其实这个我们也是可以自己做一个的,但是我们自己做的话,就是也是可以实现的,但是无法做到响应式,而Vuex就是为了提供这样一个在多个组件间共享状态并且可以做到响应式的插件。

多界面状态管理

多界面状态管理理解:

  • 多个视图都依赖同一个状态(一个状态改了,多个界面需要进行更新)
  • 不同界面的 Actions 都想修改同一个状态(Home.vue 需要修改,其他文件也需要修改这个状态)
    全局单例模式(职务就是相当于大管家)
  • 我们现在要做的就是将共享的状态抽取出来,交给我们的大管家,统一进行管理。
  • 之后,你们每个视图,按照我规定好的规定,进行访问和修改等操作。
  • 这就是Vuex背后的基本思想。

talk is cheap, show me the code

我们创建一个文件夹 src/store,并且在其中创建一个 index.js 文件,代码如下:

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

// 1.安装插件
Vue.use(Vuex)
// 2.创建对象
const store = new Vuex.Store({
    state: {
        count: 0
    },
    mutations: {
        increment(state){
            state.count++
        },
        decrement(state){
            state.count--
        }
    },
    actions: {
        
    },
    getters: {
        
    },
    modules: {
        
    }
})

// 导出store对象
export default store

其次,我们让所有的Vue组件都可以使用这个store对象

  • 来到 src/main.js文件,导入store对象,并且挂载到new Vue中
import Vue from 'vue'
import App from './App'
// 1.导入store对象
import store from './store'


new Vue({
    el: '#app',
    // 2.挂载 store
    store,
    render: h => h(App)
})
  • 这样,在其他Vue组件中,我们就可以通过 this.$store的方式,获取到这个store对象了
<template>
  <div id="app">
      <p>
      <button @click="increment">+1</button>
      <button @click="decrement">-1<</button>
  </div>
</template>


<script>
export default {
    name: 'App',
    components: {
        
    },
    computed: {
        count: function() {
            return this.$store.state.count
        }
    },
    methods: {
        increment: function() {
            this.$store.commit('increment')
        },
        decrement: function() {
            this.$store.commit('decrement')
        }
    }
}

</script>

使用步骤时候的步骤:

  • 提取出一个公共的 store 对象,用于保存在多个组件中共享的状态
  • 将 store 对象放置在 new Vue 对象中,这样可以保证在所有的组件中都可以使用到
  • 在其他组件中使用 store 对象中保存的状态即可
  • 通过 this.$store.state 属性的方式来访问状态
  • 通过 this.$store.commit('mutation中方法') 来修改状态
  • 我们通过提交 mutation 的方式,而非直接改变 store.state.count
  • 这是因为 Vuex 可以更明确的追踪状态的变化,所以不要直接改变 store.state.count 的值

Vuex的几个核心概念:

上面代码也有过了,这里再写一下

// 2.创建对象
const store = new Vuex.Store({
    //第一个重要概念 state
    state: {
    
    },
    //第二个重要概念 mutations
    mutations: {
    
        }
    },
    //第三个重要概念 actions
    actions: {
        
    },
    //第四个重要概念 getters
    getters: {
        
    },
    //第五个重要概念 modules
    modules: {
        
    }
})

解释各个重要概念

第一个重要概念 state

State 单一状态树

Vuex提出使用单一状态树, 什么是单一状态树呢?

  • 英文名称是Single Source of Truth,也可以翻译成单一数据源。
  • 举个例子,相当于把人的各种原本分散的信息如个人档案,公积金记录,社保记录都放在了一个地方处理,提高了效率。
  • 所以Vuex也使用了单一状态树来管理应用层级的全部状态。单一状态树能够让我们最直接的方式找到某个状态的片段,而且在之后的维护和调试过程中,也可以非常方便的管理和维护。

第二个重要概念 mutations

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

  • Mutation主要包括两部分:

    • 字符串的事件类型(type)
    • 一个回调函数(handler)  该回调函数的第一个参数就是 state
  1. mutation 的定义方式
import Vuex from 'vuex'
import Vue from 'vue'

// 1.安装插件
Vue.use(Vuex)
// 2.创建对象
const store = new Vuex.Store({
    state: {
        count: 0
    },
    mutations: {
        increment(state){
            state.count++
        },
        decrement(state){
            state.count--
        }
    },
    actions: {
        
    },
    getters: {
       
    },
    modules: {
        
    }
})

// 导出store对象
export default store

2.2. 通过 mutation 更新

<template>
  <div id="app">
      <button @click="increment">+1</button>
      <button @click="decrement">-1<</button>
  </div>
</template>


<script>
export default {
    name: 'App',
    components: {
        
    },
    computed: {
        count: function() {
            return this.$store.state.count
        }
    },
    methods: {
        increment: function() {
            this.$store.commit('increment')
        },
        decrement: function() {
            this.$store.commit('decrement')
        }
    }
}

</script>

Mutation传递参数

在通过mutation更新数据的时候, 有可能我们希望携带一些额外的参数,参数被称为 mutation 的载荷(Payload)

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

// 1.安装插件
Vue.use(Vuex)
// 2.创建对象
const store = new Vuex.Store({
    state: {
        count: 0
    },
    mutations: {
        increment(state){
            state.count++
        },
        decrement(state){
            state.count--
        },
        incrementCount(state,count){
            state.counter += count
        }
    },
    actions: {
        
    },
    getters: {
       
    },
    modules: {
        
    }
})

// 导出store对象
export default store

在其他 .vue 组件中来修改状态

<template>
  <div id="app">
      <button @click="increment">+1</button>
      <button @click="decrement">-1<</button>
      <button @click="addCount(5)">+5</button>
      <button @click="addCount(10)">+5</button>
  </div>
</template>


<script>
export default {
    name: 'App',
    components: {
        
    },
    methods: {
        increment: function() {
            this.$store.commit('increment')
        },
        decrement: function() {
            this.$store.commit('decrement')
        },
        addCount(count){
            this.$store.commit('incrementCount',count)
        }
    }
}

</script>

但是如果参数不是一个呢?比如我们有很多参数需要传递,这个时候,我们通常会以对象的形式传递,也就是 payload 是一个对象。

这个时候可以再从对象中取出相关的信息。

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

// 1.安装插件
Vue.use(Vuex)
// 2.创建对象
const store = new Vuex.Store({
    state: {
        students: [
            {id: 100,name: 'why',age: 18},
            {id: 111,name: 'kobe',age: 21},
            {id: 112,name: 'lucy',age: 25},
            {id: 113,name: 'lilei',age: 2},
        ]
    },
    mutations: {
        
    },
    actions: {
        
    },
    getters: {
      
    },
    modules: {
        addStudent(state,stu){
            state.students.push(stu)
        }
    }
})

// 导出store对象
export default store

在其他 .vue 组件中来修改状态

<template>
  <div id="app">
      <button @click="increment">+1</button>
      <button @click="decrement">-1<</button>
      <button @click="addCount(5)">+5</button>
      <button @click="addCount(10)">+5</button>
      <button @click="addStudent">添加学生</button>
  </div>
</template>


<script>
export default {
    name: 'App',
    components: {
        
    },
    methods: {
        increment: function() {
            this.$store.commit('increment')
        },
        decrement: function() {
            this.$store.commit('decrement')
        },
        addCount(count){
            this.$store.commit('incrementCount',count)
        },
        addStudent(){
            const stu = {id: 114, name: 'alan',age: 35}
            this.$store.commit('addStudent',stu)
        }
    }
}

</script>

Mutation提交风格

上面的通过 commit 进行提交是一种普通的方式

Vue 还提供了另外一种风格,它是一个包含 type 属性的对象

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

// 1.安装插件
Vue.use(Vuex)
// 2.创建对象
const store = new Vuex.Store({
    state: {
        students: [
            {id: 100,name: 'why',age: 18},
            {id: 111,name: 'kobe',age: 21},
            {id: 112,name: 'lucy',age: 25},
            {id: 113,name: 'lilei',age: 2},
        ]
    },
    mutations: {
        
    },
    actions: {
        
    },
    getters: {
      
    },
    modules: {
       incrementCount(state,payload){
           state.counter += payload.count
       }
    }
})

// 导出store对象
export default store

<template>
  <div id="app">
      <button @click="increment">+1</button>
      <button @click="decrement">-1<</button>
      <button @click="addCount(5)">+5</button>
      <button @click="addCount(10)">+5</button>
  </div>
</template>


<script>
export default {
    name: 'App',
    components: {
        
    },
    methods: {
        increment: function() {
            this.$store.commit('increment')
        },
        decrement: function() {
            this.$store.commit('decrement')
        },
        addCount(count){
            // 1.普通的提交封装
            // this.$store.commit('incrementCount',count)
            
            // 2.特殊的提交封装
            this.&store.commit({
                type: 'incrementCount',
                count
            })
        },
        
    }
}

</script>

Mutation响应规则

Vuex 的 store 中的 state 是响应式的,当 state 中的数据发生改变时,Vue组件会自动更新

这就要求我们必须遵守一些Vuex对应的规则:

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

我们在 index.js 中增加 info 状态

const store = new Vuex.Store({
    state: {
        info: {
            name: 'why', age: 18
        }
    },
    mutations: {
       // 方式一:Vue.set()
       Vue.set(state.info,'height',payload.height)
       // 方式二:给 info 赋值一个新的对象
       state.info = {...state.info,'height':payload.height}
    }
})

我们在其他 .vue 组件中修改状态

<template>
  <div id="app">
      <p>我的个人信息: {{info}}</p>
      <button @click="updateInfo">更新信息</button>
  </div>
</template>


<script>
export default {
    name: 'App',
    components: {
    },
    computed: {
		info(){
            return this.$store.state.info
        } 
    },
    methods: {
        updateInfo(){
            this.$store.commit('updateInfo',{height: 1.88})
        }
    }
    
}

</script>

Mutation常量类型-概念

我们来考虑下面的问题:

  • 在 mutation 中,我们定义了很多事件类型(也就是其中的方法名称)
  • 当我们的项目增大时,Vuex 管理的状态越来越多,需要更新状态的情况越来越多, 那么意味着 Mutation 中的方法越来越多
  • 方法过多,使用者需要花费大量的精力的经历去记住这些方法,甚至是多个文件间来回切换, 查看方法名称, 甚至如果不是复制的时候, 可能还会出现写错的情况.

如何避免上述问题呢?
在各种Flux实现中, 一种很常见的方案就是使用常量替代 Mutation 事件的类型
我们可以将这些常量放在一个单独的文件中, 方便管理以及让整个app所有的事件类型一目了然.

具体怎么做呢?
我们可以创建一个文件: mutation-types.js, 并且在其中定义我们的常量
定义常量时, 我们可以使用ES2015中的风格, 使用一个常量来作为函数的名称.

我们在src/store 下新建 mutation-types.js

exports const UPDATA_INFO = 'UPDATE_INFO'

这样的话我们在 index.js 中可以导入

import Vuex from 'vuex'
import Vue from 'vue'
import * as types from './mutation-types'

// 1.安装插件
Vue.use(Vuex)
// 2.创建对象
const store = new Vuex.Store({
    state: {
        info: {
            name: 'why',
            age: 18
        }
    },
    mutations: {
        [tyoes.UPDATE_INFO](state,payload){
            state.info = {...state.info,'height':payload.height}
        }
    },
    actions: {
        
    },
    getters: {
      
    },
    modules: {
       
    }
})

// 导出store对象
export default store

我们在其他 .vue 组件中也可以使用

<template>
  <div id="app">
      <p>我的个人信息: {{info}}</p>
      <button @click="updateInfo">更新信息</button>
  </div>
</template>


<script>
import {UPDATE_INFO} from "./store/mutation-types"
export default {
    name: 'App',
    components: {
    },
    computed: {
		info(){
            return this.$store.state.info
        } 
    },
    methods: {
        updateInfo(){
            this.$store.commit('UPDATE_INFO',{height: 1.88})
        }
    }
    
}

</script>

Mutation同步函数

通常情况下,Vuex 要求我们 Mutation 中的方法必须是同步方法

  • 主要的原因是当我们使用 devtools 时,devtools 可以帮助我们捕捉 mutation 的快照
  • 但是如果是异步操作,那么 devtools 将不能很好的追踪这个操作什么时候会被完成。 So,通常情况下,不要在 mutation 中进行异步的操作,而是使用下面的action

第三个重要概念 actions

Action的基本定义

我们强调,不要在 Mutation 中进行异步操作,但是某些情况,我们确实希望在 Vuex 中进行一些异步操作,比如网络请求,必然是异步的,Action 类似于 Mutation,但是是用来代替 Mutation 进行异步操作的。

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


// 1.安装插件
Vue.use(Vuex)
// 2.创建对象
const store = new Vuex.Store({
    state: {
       count: 0  
    },
    mutations: {
        increment(state){
            state.count++
        }
    },
    actions: {
        increment(context){
            context.commit('increment')
        }
    },
    getters: {
      
    },
    modules: {
       
    }
})

// 导出store对象
export default store

context 是什么?

  • context 是 store 对象具有相同方法和属性的对象
  • 也就是说,我们可以通过 context 去进行 commit 相关的操作,也可以获取 context.state 等。

Action的分发

在Vue组件中,如果我们调用 action 中的方法,那么就需要使用 dispatch

methods: {
    increment(){
        this.$store.dispatch('increment')
    }
}

同样的,也是支持传递 payload

methods: {
    increment(){
        this.$store.dispatch('increment', {cCount: 5})
    }
}

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


// 1.安装插件
Vue.use(Vuex)
// 2.创建对象
const store = new Vuex.Store({
    state: {
       count: 0  
    },
    mutations: {
        increment(state,payload){
            state.count += payload.cCount
        }
    },
    actions: {
        increment(context,payload){
            setTimeout(() => {
                context.commit('increment',payload)
            },1000)
        }
    },
    getters: {
      
    },
    modules: {
       
    }
})

// 导出store对象
export default store

Action返回的Promise

在 Action 中,我们可以将异步操作放在一个 Promise 中,并且在成功或者失败之后,调用对应的 resolve 或 reject

actions: {
    increment(context){
        return new Promise((resolve) => {
            setTimeout(() => {
                context.commit('increment')
                resolve()
            },1000)
        })
    }
},

在其他 Vue 组件中

methods: {
    increment() {
        this.$store.dispatch('increment').then(res => {
            console.log('完成了更新操作');
        })
    }
}

第四个重要概念 getters

有时候,我们需要从 store 中获取一些 state 变异后的状态,比如下面的 Store

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

// 1.安装插件
Vue.use(Vuex)
// 2.创建对象
const store = new Vuex.Store({
    state: {
        students: [
            {id: 100,name: 'why',age: 18},
            {id: 111,name: 'kobe',age: 21},
            {id: 112,name: 'lucy',age: 25},
            {id: 113,name: 'lilei',age: 2},
        ]
    },
    mutations: {
        
    },
    actions: {
        
    },
    getters: {
       // 获取年龄大于20的学生对象
       more20stu(state){
           return state.students.filter(s => s.age >20)
       } 
    },
    modules: {
        
    }
})

// 导出store对象
export default store

这样我们在其他 .vue 组件中也可以拿到年龄大于20的学生对象

<template>
  <div>
      <h2>{{$store.getters.more20stu}}</h2>
  </div>
</template>

<script>
// @ is an alias to /src
import HelloWorld from '@/components/HelloWorld.vue'

export default {
  name: 'Home',
  components: {
    HelloWorld
  }
}
</script>

Getters作为参数和传递参数

如果我们已经有了一个获取所有年龄大于20岁学生的列表 getters,那么代码可以这样来写

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

// 1.安装插件
Vue.use(Vuex)
// 2.创建对象
const store = new Vuex.Store({
    state: {
        students: [
            {id: 100,name: 'why',age: 18},
            {id: 111,name: 'kobe',age: 21},
            {id: 112,name: 'lucy',age: 25},
            {id: 113,name: 'lilei',age: 2},
        ]
    },
    mutations: {
        
    },
    actions: {
        
    },
    getters: {
       // 获取年龄大于20的学生对象
       more20stu(state){
           return state.students.filter(s => s.age >20)
       },
       // 获取年龄大于20的学生个数
       more20stuLength(state,getters) {
           return getters.more20stu.length
       }
    },
    modules: {
        
    }
})

// 导出store对象
export default store

getters 默认是不能传递参数的, 如果希望传递参数, 那么只能让getters本身返回另一个函数.

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

// 1.安装插件
Vue.use(Vuex)
// 2.创建对象
const store = new Vuex.Store({
    state: {
        students: [
            {id: 100,name: 'why',age: 18},
            {id: 111,name: 'kobe',age: 21},
            {id: 112,name: 'lucy',age: 25},
            {id: 113,name: 'lilei',age: 2},
        ]
    },
    mutations: {
        
    },
    actions: {
        
    },
    getters: {
       // 获取年龄大于20的学生对象
       more20stu(state){
           return state.students.filter(s => s.age >20)
       },
       // 获取年龄大于20的学生个数
       more20stuLength(state,getters) {
           return getters.more20stu.length
       },
       // 让用户自己决定获取年龄大于多少
       moreAgeStu(state) {
           return function(age){
               return state.students.filter(s => s.age > age)
           }
       }
    },
    modules: {
        
    }
})

// 导出store对象
export default store

这样我们在其他 .vue 组件中就可以传入年龄数值筛选了

<template>
  <div>
      <h2>{{$store.getters.more20stu}}</h2>
      <h2>{{$store.getters.more20stuLength}}</h2>
      <h2>{{$store.getters.moreAgeStu(8)}}</h2>
  </div>
</template>

<script>
// @ is an alias to /src
import HelloWorld from '@/components/HelloWorld.vue'

export default {
  name: 'Home',
  components: {
    HelloWorld
  }
}
</script>

第五个重要概念 modules

认识 Module Module 是模块的意思,为什么在 Vuex 中我们要使用模块呢?

  • Vue 使用单一状态树,那么也意味着很多状态都会交给 Vuex 来管理
  • 当应用变得非常复杂时, store 对象就有可能变得相当臃肿
  • 为了解决这个问题,Vuex 允许我们将 store 分割成模块,而每个模块拥有自己的 state、mutations、action、getters等
const ModuleA = {
    state: {},
    mutations: {},
    actions: {},
    getters: {}
}

const ModuleB = {
    state: {},
    mutations: {},
    actions: {},
}

const store = new Vuex.Store({
    modules: {
        a: moduleA,
        b: moduleB
    }
})

store.state.a		// -> moduleA 的状态
store.state.b		// -> moduleB 的状态

小结

下面用两个图来总结一下

image.png

image.png

  • state,action,mutations,getters,mudules。这五个概念都是要明白的,最终这个的目标都是为了更好的状态共享