完全理解Vuex五大核心概念,并学会使用
.Vuex核心概念
一、State单一状态树
提供一个响应式数据,存贮公共数据的地方
Vuex提出使用单—状态树,什么是单—状态树呢?
英文名称是Single Source of Truth,也可以翻译成单—数据源。
即用一个对象就包含了全部的状态数据。也就是说如果我们定义了一个 store 的实例,那么这个 store 实例里面只有一个 state。state作为构造器选项,定义了所有我们需要的基本状态参数。
这个和我们在应用开发中比较类似︰
如果你的状态信息是保存到多个Store对象中的,那么之后的管理和维护等等都会变得特别困难。所以Vuex也使用了单—状态树来管理应用层级的全部状态。
单一状态树能够让我们最直接的方式找到某个状态的片段,而且在之后的维护和调试过程中,也可以非常方便的管理和维护。
const store = new Vuex.Store({
state: {
// 提供一个响应式数据,存贮公共数据的地方
counter: 100
}
})
二、Getter
从基本数据(state)派生的数据,相当于state的计算属性
组件:
<template>
<div id="app">
<h1>---------- $store.getters -----</h1>
<h2>counter平方:{{ $store.getters.powerCounter }}</h2>
<h2>及格学生名单:{{ $store.getters.passStudents }}</h2>
<h2>及格人数:{{ $store.getters.passStudentsLength }}</h2>
<h2>动态获取:{{ $store.getters.dynamicStudents(50) }}</h2>
</div>
</template>
效果:
counter平方:10000
及格学生名单:[ "张三", "李四", "王五", "赵柳" ]
及格人数:4
动态获取:[ "张三", "李四", "王五", "赵柳", "陈琦" ]
getters:
const store = new Vuex.Store({
state: {
// 提供一个响应式数据,存贮公共数据的地方
counter: 100,
students: [
{ name: "张三", age: 18, fraction: 99 },
{ name: "李四", age: 23, fraction: 82 },
{ name: "王五", age: 22, fraction: 76 },
{ name: "赵柳", age: 19, fraction: 65 },
{ name: "陈琦", age: 25, fraction: 55 },
]
},
getters: {
// 从基本数据(state)派生的数据,相当于state的计算属性
powerCounter(state) {
return Math.pow(state.counter, 2)
},
passStudents(state) {
// 需求:获取及格的人名集合
let passArray = []
state.students.filter(item => item.fraction >= 60).map(val => passArray.push(val.name))
console.log(state.students);
return passArray
},
passStudentsLength(state, getters) {
// 需求:获取及格的人有多个
// 传入第二个参数getters
return getters.passStudents.length
},
dynamicStudents(state) {
// ****计算属性传参****
// 需求:动态获取分数线上的人有多个,传入50,就返回50分以上有哪些人
// 注意:这里不能直接向此计算属性传入第二个参数fraction了,如dynamicStudents(state,fraction),因为默认第二个参数是getters
// 需要向此属性传入参数,那么就返回一个函数给它,让return的函数去接受那个fraction参数
return (fraction) => {
let passArray = []
state.students.filter(item => item.fraction >= fraction).map(val => passArray.push(val.name))
return passArray
}
}
},
})
三、Mutation状态更新
1)更改state方法;
Vuex的store状态的更新唯一方式:提交Mutation,其主要包括两部分∶
字符串的事件类型( type )、一个回调函数( handler ),该回调函数的第一个参数就是state。
2)定义mutation的方式:
const store = new Vuex.Store({
state: {
counter: 100,
},
mutations: {
increment(state) { // 变更状态,更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。
state.counter++
},
decrement(state) {
state.counter--
}
},
})
3)通过mutation提交更新
<script>
export default {
name: "App",
methods: {
addition() {
this.$store.commit("increment");
},
subtraction() {
this.$store.commit("decrement");
}
}
};
</script>
4)mutation在提交时如何传传递参数,以及传递多个参数?
在通过mutation更新数据的时候,有可能我们希望携带一些额外的参数参数被称为是mutation的载荷(Payload)
我们通常会以对象的形式传递,也就是payload是一个对象.这个时候可以再从对象中取出相关的信息.
4.1)传一个参数的写法(官网例子):
// ...
mutations: {
increment (state, n) {
state.count += n
}
}
store.commit('increment', 10)
4.2)对象风格的提交方式,传多个参数的写法:
此时参数不能继续在后面加,后面的参数无效,传进去的参数为undefined;
官网的解释:In most cases, the payload should be an object so that it can contain multiple fields, and the recorded mutation will also be more descriptive;在大多数情况下,有效载荷应该是一个对象,以便它可以包含多个字段,并且记录的变异也将更具描述性.
所以,我们可以将参数以对象的方式传进去,多个属性就是多个参数了。
// ...
mutations: {
incrementCount(state, payload) {
console.log('payload: ', payload);
// payload: {type: 'incrementCount', count: 5, age: 18, name: 'chenxx'}
state.counter += payload.count
}
}
// ...
addCount(count) {
// 1、普通的提交封装
this.$store.commit("incrementCount", count);
// 2、特殊的提交封装(传递多个参数)
this.$store.commit({
type: "incrementCount",
count,
age:18,
name:"chenxx"
});
},
links:参考文章
5)Mutation响应规则
Vuex的store中的state是响应式的,当state中的数据发生改变时,Vue组件会自动更新.这就要求我们必须遵守一些Vuex对应的规则:
提前在store中初始化好所需的属性.
当给state中的对象添加新属性时,使用下面的方式:
mutations: {
modifyMsg(state) {
//修改信息
state.info.name = "info陈"//---响应式
//新增
state.info["sex"] = "man"//---不能做到响应式
Vue.set(state.info, "sex", "man");//---Vue.set方法,响应式
// 删除
delete state.info.age //---不能做到响应式
Vue.delete(state.info, "age");//---Vue.delete,响应式
},
}
6)使用常量替代 Mutation 事件类型
使用常量替代 mutation 事件类型在各种 Flux 实现中是很常见的模式。这样可以使 linter 之类的工具发挥作用,同时把这些常量放在单独的文件中可以让你的代码合作者对整个 app 包含的 mutation 一目了然:
Vuex中为了代码看起来简洁清晰,常常使用常量替代Mutation事件类型
mutation-types.js
export const INCREMENT = "increment";
export const DECREMENT = "decrement";
export const INCREMENTCOUNT = "incrementCount";
export const INCREMENTSTUDENT = "incrementStudent";
export const MODIFYMSG = "modifyMsg";
store.js
import * as mutationTypes from './mutation-types' //导入模块,并给以别名mutationTypes
const store = new Vuex.Store({
mutations: {
// 我们可以使用 ES2015 风格的计算属性命名功能来使用一个常量作为函数名
[mutationTypes.INCREMENT](state) {
state.counter++
},
[mutationTypes.DECREMENT](state) {
state.counter--
}
}
})
App.vue
<script>
import * as mutationTypes from './store/mutation-types'
export default {
name: "App",
methods: {
addition() {
this.$store.commit(mutationTypes.INCREMENT);
},
subtraction() {
this.$store.commit(mutationTypes.DECREMENT);
}
// mutationTypes.INCREMENT是个常量键,
// 不能把它作为字符串,不然它会取不到mutation-types.js中定义的INCREMENT的常量值。
}
};
</script>
用不用常量取决于你——在需要多人协作的大型项目中,这会很有帮助。但如果你不喜欢,你完全可以不这样做。
link:参考文章
7)Mutation 必须是同步函数
一条重要的原则就是要记住 mutation 必须是同步函数。为什么?
通常情况下, Vuex要求我们Mutation中的方法必须是同步方法.主要的原因是当我们使用devtools时,可以devtools可以帮助我们捕捉mutation的快照.但是如果是异步操作,那么devtools将不能很好的追踪这个操作什么时候会被完成.
mutations: {
setTimeout(() => {
state.counter++
}, 1000)
// devtools将无法跟踪counter的状态变化
}
四、Action
1)放的是异步的操作 通过dispatch的方法让action里面的方法执行
Action 类似于 mutation,不同在于:
-
Action 提交的是 mutation,而不是直接变更状态。
-
Action 可以包含任意异步操作。
context:上下文↓,这里得上下文是store:
https://www.cnblogs.com/echolun/p/11438363.html
2)最简单的方式
const store = new Vuex.Store({
actions: {
infoUpdateActions(context, payload) {
setTimeout(() => {
context.commit("modifyMsg")
console.log('payload: ', payload);
}, 1000);
},
})
<script>
export default {
methods: {
modifyMsgActions() {
// 最简易
this.$store.dispatch("infoUpdateActions", "payload---Obj");
},
},
};
</script>
3)使用payload对象形式
const store = new Vuex.Store({
actions: {
infoUpdateActions(context,payload) {
// payload对象形式
setTimeout(() => {
context.commit("modifyMsg")
console.log('payload: ', payload.message);
payload.success()
}, 1000);
},
})
<script>
export default {
methods: {
modifyMsgActions() {
// payload对象形式
this.$store.dispatch("infoUpdateActions", {
message: "payload---Obj",
success() {
console.log("commit-over1");
},
});
}
}
};
</script>
4)使用Promise .then
const store = new Vuex.Store({
actions: {
infoUpdateActions(context, payload) {
// Promise
return new Promise((resolve) => {
setTimeout(() => {
context.commit("modifyMsg")
console.log('payload: ', payload);
resolve("commit-over")
}, 1000);
})
},
})
<script>
export default {
methods: {
modifyMsgActions() {
// Promise
this.$store
.dispatch("infoUpdateActions", "payload-Object")
.then((data) => {
console.log(data);
});
},
},
};
</script>
五、Module
模块化vuex
可以让每一个模块拥有自己的state、mutation、action、getters,使得结构非常清晰,方便管理。
由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割:
const moduleA = {
state: () => ({ ... }),
mutations: { ... },
actions: { ... },
getters: { ... }
}
const store = new Vuex.Store({
modules: {
moduleA
}
})
store.state.moduleA // -> moduleA 的状态
1) moduleA
state、getters、
const moduleA = {
// 对于模块内部的 mutation 和 getter,接收的第一个参数是模块的局部状态对象。
state: () => ({
message: "Message"
// 箭头函数不是()=>{}吗,这个怎么多了个()??
// 这样写实为了给箭头函数返回JSON对象,如果不加(),那么{}中的内容将会被当做代码,这样js语法错误。
// <h2>modules:{{ $store.state.moduleA.message }}</h2>,在使用时有moduleA的前缀
}),
getters: {
// modules-getters带一个参数:state
updateMsgModule1(state) {
return state.message + "参数:__state"
},
// modules-getters带两个参数:state与moduleA的getters
updateMsgModule2(state, getters) {
return getters.updateMsgModule1 + "__getters"
},
// modules-getters带三个参数:state、getters和roteState(根的state)
updateMsgModule3(state, getters, roteState) {
return state.message + "参数:__roteState" + roteState.counter
},
},
}
mutations、actions
const moduleA = {
mutations: {
// modules的mutations使用与root的一样
moudleChangeMsg(state, payload) {
state.message = payload
}
},
actions: {
// context 此处上下文指moduleA,使用与root一致,只不过上下文指代不同
// actions写法1:
moduleAactionsTest(context, payload) {
console.log(context);
return new Promise((resolve) => {
setTimeout(() => {
context.commit("moudleChangeMsg", payload)
resolve("moduleAactions--over")
}, 1000);
})
},
// actions写法2:对象的解构赋值context--->>>state、commit、rootState
moduleAactionsTest ({ state, commit, rootState }) {
if ((state.count + rootState.count) % 2 === 1) {
commit('increment')
}
}
},
modules: {
// 又可以往下嵌套....
},
}
2) App
<template>
<div id="app">
<h1>---------- $store.modules -----</h1>
<h2>modules:{{ $store.state.moduleA.message }}</h2>
<button @click="modifyModuleAMsg">修改moduleA信息</button>
<button @click="asyncModifyModuleAMsg">异步修改moduleA信息</button>
<h2>moduleAgetters1————{{ $store.getters.updateMsgModule1 }}</h2>
<h2>moduleAgetters2————{{ $store.getters.updateMsgModule2 }}</h2>
<h2>moduleAgetters3————{{ $store.getters.updateMsgModule3 }}</h2>
</div>
</template>
<script>
export default {
name: "App",
methods: {
// modules↓↓↓↓
modifyModuleAMsg() {
this.$store.commit("moudleChangeMsg", " payloadIns");
},
asyncModifyModuleAMsg() {
this.$store
.dispatch("moduleAactionsTest", " payloadIns")
.then((data) => {
console.log(data);
});
},
},
};
</script>
6.项目结构:Vuex下index的模块分离
Vuex 并不限制你的代码结构。但是,它规定了一些需要遵守的规则:
- 应用层级的状态应该集中到单个 store 对象中。
- 提交 mutation 是更改状态的唯一方法,并且这个过程是同步的。
- 异步逻辑都应该封装到 action 里面。
只要你遵守以上规则,如何组织代码随你便。如果你的 store 文件太大,只需将 action、mutation 和 getter 分割到单独的文件。
对于大型应用,我们会希望把 Vuex 相关代码分割到模块中。下面是项目结构示例:
└── store
├── index.js # 我们组装模块并导出 store 的地方
├── actions.js # 根级别的 action
├── getters.js # 根级别的 getters
├── mutations.js # 根级别的 mutation
├── mutation-types.js # mutation 事件类型常量
└── modules
├── index.js # 所有模块组装的地方
└── moduleA.js # moduleA模块
我们在第一步就进行了index的抽离,但是我们发现,如果把所有的状态、方法、异步处理等都写在index.js下,后期处理越来越复杂时,管理起来会非常麻烦,那么我们参照官网,对index再做一层抽离.
link:官网案例
以上是我的store目录结构,与官网有所差别,但是逻辑是大同小异的,具体抽离根据需求决定。然后import组装就可以了↓
// store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
import getters from './getters'
import mutations from './mutations'
import actions from './actions'
import modules from './modules/index'
// 1、安装插件
Vue.use(Vuex)
// 2、创建Store对象,这里并不是直接使用new Vuex,二是使用其中的一个Store类
const store = new Vuex.Store({
state: {
counter: 100,
// ....一般,state不进行抽离,因为Vuex的目的就是管理状态,这样更加直观。
},
getters,
mutations,
actions,
modules,
})
// 3、导出对象
export default store
六、总结
学习Vuex时,掌握好五大基本核心概念十重要,其实每一个概念并不是很难,我们只需要掌握其用法、概念以及他们之间的关系就没什么大问题。最后对于文件的抽离也体现了Vue模块化的思想,如果能理解这一点,对于以后的开发会有很大的帮助,最后祝大家完全掌握这个技能点!