Vuex
Vuex
Vuex是做什么的?
- 官方解释:Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。
它采用 集中式存储管理 应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
Vuex 也集成到 Vue 的官方调试工具 devtools extension,提供了诸如零配置的 time-travel 调试、状态快照导入导出等高级调试功能。
- 状态管理到底是什么?
状态管理模式、集中式存储管理这些名词听起来就非常高大上,让人捉摸不透。
其实,你可以简单的将其看成把需要多个组件共享的变量全部存储在一个对象里面。
然后,将这个对象放在顶层的Vue实例中,让其他组件可以使用。
那么,多个组件是不是就可以共享这个对象中的所有变量属性了呢?
等等,如果是这样的话,为什么官方还要专门出一个插件Vuex呢?难道我们不能自己封装一个对象来管理吗?
当然可以,只是我们要先想想VueJS带给我们最大的便利是什么呢?没错,就是响应式。
如果你自己封装实现一个对象能不能保证它里面所有的属性做到响应式呢?当然也可以,只是自己封装可能稍微麻烦一些。
不用怀疑,Vuex就是为了提供这样一个在多个组件间共享状态的插件,用它就可以了。
管理什么状态呢****?
但是,有什么状态时需要我们在多个组件间共享的呢?
如果你做过大型开放,你一定遇到过多个状态,在多个界面间的共享问题。
比如用户的登录状态、用户名称、头像、地理位置信息等等。
比如商品的收藏、购物车中的物品等等。
这些状态信息,我们都可以放在统一的地方,对它进行保存和管理,而且它们还是响应式的
单界面的状态管理
我们知道,要在单个组件中进行状态管理是一件非常简单的事情
什么意思呢?我们来看下面的图片。
这图片中的三种东西,怎么理解呢?
State:不用多说,就是我们的状态。(你姑且可以当做就是data中的属性)
View:视图层,可以针对State的变化,显示不同的信息。(这个好理解吧?)
Actions:这里的Actions主要是用户的各种操作:点击、输入等等,会导致状态的改变。
小案例:
在这个案例中,我们有木有状态需要管理呢?没错,就是个数counter。
counter需要某种方式被记录下来,也就是我们的State。
counter目前的值需要被显示在界面中,也就是我们的View部分。
界面发生某些操作时(我们这里是用户的点击,也可以是用户的input),需要去更新状态,也就是我们的Actions
这不就是上面的流程图了吗?
多界面状态管理
Vue已经帮我们做好了单个界面的状态管理,但是如果是多个界面呢?
多个试图都依赖同一个状态(一个状态改了,多个界面需要进行更新)
不同界面的Actions都想修改同一个状态(Home.vue需要修改,Profile.vue也需要修改这个状态)
也就是说对于某些状态(状态1/状态2/状态3)来说只属于我们某一个视图,但是也有一些状态(状态a/状态b/状态c)属于多个试图共同想要维护的
状态1/状态2/状态3你放在自己的房间中,你自己管理自己用,没问题。
但是状态a/状态b/状态c我们希望交给一个大管家来统一帮助我们管理!!!
没错,Vuex就是为我们提供这个大管家的工具。
全局单例模式(大管家)
我们现在要做的就是将共享的状态抽取出来,交给我们的大管家,统一进行管理。
之后,你们每个视图,按照我规定好的规定,进行访问和修改等操作。
这就是Vuex背后的基本思想。
Devtools
插件需要到谷歌商店里下载。
小案例2:
在之前案例的基础上:
安装vuex
npm install vuex --save
首先,我们需要在某个地方存放我们的Vuex代码:
这里,我们先创建一个文件夹store,并且在其中创建一个index.js文件
在index.js文件中写入如下代码:
import Vuex from 'vuex'
import Vue from 'vue'
//2、使用插件
Vue.use(Vuex)
//3.new一个Vuex.store对象出来
const store=new Vuex.Store({
state:{
count:0
},
mutations:{
increment(state) {
state.counte++
},
decrement(state) {
state.counte--
}
},
})
//4.导出
export default store
挂载到Vue实例中
其次,我们让所有的Vue组件都可以使用这个store对象
来到 main.js 文件,导入store对象,并且放在new Vue中
这样,在其他Vue组件中,我们就可以通过this.$store的方式,获取到这个store对象了
使用Vuex的count
使用方法:
1、可以直接$store.state.count
就是通过组件直接修改状态,但是不建议,这样无法保留状态修改记录,跳过了Devtools,一旦出错,很难找到
2、使用mutations
先定义对应的方法,再到对应组件里使用该方法
import Vuex from 'vuex'
import Vue from 'vue'
Vue.use(Vuex)
const store=new Vuex.Store({
state:{
count:0
},
mutations:{
increment(state) {
state.count++
},
decrement(state) {
state.count--
}
},
})
export default store
<template>
<div id="app">
<p>{{ count }}</p>
<button @click="increment">+1</button>
<button @click="decrement">-1</button>
<h2>-----------hello---------</h2>
<hello-world></hello-world>
</div>
</template>
<script>
import HelloWorld from "./components/HelloWorld.vue";
export default {
name: "App",
components: {
HelloWorld,
},
computed: {
count: function () {
return this.$store.state.count;
},
},
methods: {
increment: function () {
this.$store.commit("increment");
},
decrement: function () {
this.$store.commit("decrement");
},
},
};
</script>
<style>
</style>
小结:
1.提取出一个公共的store对象,用于保存在多个组件中共享的状态
2.将store对象放置在new Vue对象中,这样可以保证在所有的组件中都可以使用到
3.在其他组件中使用store对象中保存的状态即可
通过this.$store.state.属性的方式来访问状态
通过this.$store.commit('mutation中方法')来修改状态
注意事项:
我们通过提交mutation的方式,而非直接改变store.state.count。
这是因为Vuex可以更明确的追踪状态的变化,所以不要直接改变store.state.count的值。
Getters基本使用
有时候,我们需要从store中获取一些state变异后的状态,就像之前我们学的组件里面的computed属性一样
我们可以在Store中定义getters
案例:
index.js
import Vuex from 'vuex'
import Vue from 'vue'
Vue.use(Vuex)
const store=new Vuex.Store({
state:{
count:0,
students:[
{id:110,name:'Why',age:18},
{id:111,name:'kobe',age:21},
{id:112,name:'luck',age:25},
{id:113,name:'lilei',age:30},
]
},
getters:{
more20stu(state) {
return state.students.filter(s=>s.age>=20)
},
agefilter(state) {
return function(age){
return state.students.filter(s=>s.age>age)
}
}
},
mutations:{
increment(state) {
state.count++
},
decrement(state) {
state.count--
}
},
})
export default store
组件使用
<template>
<div class="test">
<div>当前计数:{{ $store.state.count }}</div>
<h2>{{ $store.getters.more20stu }}</h2>
<h2>{{ $store.getters.agefilter(25) }}</h2>
<!-- <button @click="increment">+</button>
<button @click="decrement">-</button> -->
</div>
</template>
<script>
export default {
name: "HelloWorld",
data() {
return {
counter: 0,
};
},
components: {},
};
</script>
<style>
</style>
** 总结:**
通过getters属性,定义两个函数
1、无参数
more20stu(state) {
return state.students.filter(s=>s.age>=20)
}
2、用户可以动态的决定筛选的年龄范围
agefilter(state) {
return function(age){
return state.students.filter(s=>s.age>age)
}
}
注意:getters默认是不能传递参数的, 如果希望传递参数, 那么只能让getters本身返回另一个函数.
组件使用方法:$store.getters.方法名
1、{{ $store.getters.more20stu }}
2、{{ $store.getters.agefilter(25) }}
Mutation使用
基本更新
Vuex的store状态的更新唯一方式:提交Mutation
Mutation主要包括两部分:
- 字符串的事件类型(type)
- 一个回调函数(handler),该回调函数的第一个参数就是state。
mutation的定义方式:
mutations:{
increament(state) {
state.count++
}
}
通过mutation更新
increament:function() {
this.$store.commit('increament')
}
Mutation传递参数
在通过mutation更新数据的时候, 有可能我们希望携带一些额外的参数
参数被称为是mutation的载荷(Payload)
Mutation中的代码:
decrement(state,n) {
state.count-=n
}
decrement:function() {
this.$store.commit('decrement',2)
}
如果有多个参数:
changeCount(state,payload){
state.count=payload.count;
}
changeCount: function () {
this.$store.commit("changeCount", { count: 0 });
},
Mutation提交风格
上面的通过commit进行提交是一种普通的方式
Vue还提供了另外一种风格, 它是一个包含type属性的对象
this.$store.commit({
type:'changecount',
count:100
})
Mutation响应规则
Vuex的store中的state是响应式的, 当state中的数据发生改变时, Vue组件会自动更新.
这就要求我们必须遵守一些Vuex对应的规则:
提前在store中初始化好所需的属性.
当给state中的对象添加新属性时, 使用下面的方式:
方式一: 使用Vue.set(obj, 'newProp', 123)
方式二: 用新对象给旧对象重新赋值
mutations:{
updateInfo(state,ppayload) {
//方式一:Vue.set()
Vue.set(state.info,'height',payload.height)
//方式二:给info赋值一个新对象
state.info={...state.info,'height':payload.height}
}
}
方式一和方式二都可以让state中的属性是响应式的,Vue组件会自动更新
删除一个属性(响应式):
Vue.delete(state.info,'age')
注意:通过以下方式是不能实现响应式的
state.info.name='ff'
Mutation常量类型概念
我们来考虑下面的问题:
在mutation中, 我们定义了很多事件类型(也就是其中的方法名称).
当我们的项目增大时, Vuex管理的状态越来越多, 需要更新状态的情况越来越多, 那么意味着Mutation中的方法越来越多.
方法过多, 使用者需要花费大量的经历去记住这些方法, 甚至是多个文件间来回切换, 查看方法名称, 甚至如果不是复制的时候, 可能还会出现写错的情况.
如何避免上述的问题呢?
在各种Flux实现中, 一种很常见的方案就是使用常量替代Mutation事件的类型****.
我们可以将这些常量放在一个单独的文件中, 方便管理以及让整个app所有的事件类型一目了然.
具体怎么做呢?
我们可以创建一个文件: mutation-types.js, 并且在其中定义我们的常量.
定义常量时, 我们可以使用ES2015中的风格, 使用一个常量来作为函数的名称.
1、在store文件下建一个mutation_types.js文件
export const INCREMENT ='increment'
2、使用
(1)导入
import * as types from './mutation-types.js'
(2)muation中使用
[types.INCREMENT](state) {
state.count++
},
组件中使用
increment: function () {
this.$store.commit(INCREMENT);
},
Mutation****同步函数
通常情况下, Vuex要求我们Mutation中的方法必须是同步方法.
主要的原因是当我们使用devtools时, 可以devtools可以帮助我们捕捉mutation的快照.
但是如果是异步操作, 那么devtools将不能很好的追踪这个操作什么时候会被完成.
Action****的基本定义
我们强调, 不要再Mutation中进行异步操作.
但是某些情况, 我们确实希望在Vuex中进行一些异步操作, 比如网络请求, 必然是异步的. 这个时候怎么处理呢?
Action类似于Mutation, 但是是用来代替Mutation进行异步操作的.
Action的基本使用代码如下:
context是什么?
context是和store对象具有相同方法和属性的对象.
也就是说, 我们可以通过context去进行commit相关的操作, 也可以获取context.state等.
但是注意, 这里它们并不是同一个对象, 为什么呢? 我们后面学习Modules的时候, 再具体说.
这样的代码是否多此一举呢?
我们定义了actions, 然后又在actions中去进行commit, 这不是脱裤放屁吗?
事实上并不是这样, 如果在Vuex中有异步操作, 那么我们就可以在actions中完成了.
在Vue组件中, 如果我们调用action中的方法, 那么就需要使用dispatch
同样的, 也是支持传递payload
aUpdateInfo(context, payload) {
setTimeout(() => {
context.commit('updateInfo')
console.log(payload.message);
payload.success()
}, 1000)
},
this.$store.dispatch("updateInfo", "我是携带的信息")
Action返回的Promise
前面我们学习ES6语法的时候说过, Promise经常用于异步操作.
在Action中, 我们可以将异步操作放在一个Promise中, 并且在成功或者失败后, 调用对应的resolve或reject.
actions:{
updateInfo(context,payload) {
return new Promise((resolve,reject)=>{
setTimeout(()=>{
context.commit('updateInfo');
console.log(payload);
resolve('1111')
},1000)
})
}
}
updateInfo() {
this.$store.dispatch("updateInfo", "我是携带的信息").then((res) => {
console.log("里面完成了提交");
console.log(res);
});
},
结果:
认识****Module
Module是模块的意思, 为什么在Vuex中我们要使用模块呢?
Vue使用单一状态树,那么也意味着很多状态都会交给Vuex来管理.
当应用变得非常复杂时,store对象就有可能变得相当臃肿.
为了解决这个问题, Vuex允许我们将store分割成模块(Module), 而每个模块拥有自己的state、mutations、actions、getters等
modulesA.js
export default {
state:{
name:'fanfei'
},
mutations:{
updateName(state,payload) {
state.name=payload
}
},
getters:{
fullname(state) {
return state.name+'111'
},
fullname2(state,getters){
return getters.fullname+'222'
},
fullname3(state,getters,rootState) {
return getters.fullname2+rootState.count
}
},
actions:{
aUpdtateName(coontext) {
console.log(context);
setTimeout(()=>{
context.commit('updateName','wangwu')
},1000)
}
}
}
index.js
import modulesA from './modules/moduleA'
const store=new Vuex.Store({
state,
getters,
mutations,
actions,
modules:{
a:modulesA
}
})
使用app.js
$.store.state.a.属性 //->moduleA的状态
$.store.state.b.属性 //->moduleB的状态
Module局部状态
上面的代码中, 我们已经有了整体的组织结构, 下面我们来看看具体的局部模块中的代码如何书写.
我们在moduleA中添加state、mutations、getters
mutation和getters接收的第一个参数是局部状态对象
注意:
虽然, 我们的doubleCount和increment都是定义在对象内部的.
但是在调用的时候, 依然是通过this.$store来直接调用的.
Actions的写法
actions的写法呢? 接收一个context参数对象
局部状态通过 context.state 暴露出来,根节点状态则为 context.rootState
如果getters中也需要使用全局的状态, 可以接受更多的参数
项目结构
当我们的Vuex帮助我们管理过多的内容时, 好的项目结构可以让我们的代码更加清晰.