想利用暑假时间好好学习一下vue,会记录每一天的学习内容。
今天是学习vue的第15
天!
起起伏伏乃人生常态,继续加油~
学习内容
1. Vuex核心概念
State
Getters
Mutations
Actions
Modules
关于State
的部分昨天已经学完啦~今天从Getters
开始
Getters
Vuex
允许我们在store
中定义getter
,可以认为是store
的计算属性
Getters基本使用
有时候我们需要从store
中获取一些state
变化后的状态
比如下例:获取年龄大于20的学生
const store = new Vuex.Store({
state: {
students: [
{id: 110, name: 'AIpoem1', age: 18},
{id: 111, name: 'AIpoem2', age: 21},
{id: 112, name: 'AIpoem3', age: 25},
{id: 113, name: 'AIpoem4', age: 30},
]
}
})
我们可以在store
中定义getters
:
getters: {
greaterAge(state) {
return state.students.filter(s => s.age>20);
}
},
在界面中调用:
<h2>年龄大于20的学生是:{{$store.getters.greaterAge}}</h2>
在computed
里定义方法也是可以的
computed: {
greaterAge() {
return this.$store.state.students.filter(s => s.age>20);
}
},
Getters作为参数和传递参数
Getters作为参数:
如果此时要获取年龄大于20的学生的学生个数:
getters: {
greaterAge(state) {
return state.students.filter(s => s.age>20);
},
// 其实就是return state.students.filter(s => s.age>20).length;
greaterAgeLength(state, getters) {
return getters.greaterAge.length;
}
},
Getters传递参数:
getters
默认是不能传递参数的,如果希望传递参数,只能让getters
本身返回另一个函数
比如上例中,我们希望根据ID获取用户的信息
stuByID(state) {
return function(id) {
return state.students.find(s => s.id === id);
}
}
<!--获取id为110的用户信息-->
<p>{{$store.getters.stuByID(110)}}</p>
Mutations
Mutations状态更新
Mutation
主要包括两部分:
- 字符串的事件类型
(type)
- 一个回调函数
(handler)
,该回调函数的第一个参数就是state
mutation
的定义方式:
mutations: {
increment(state) {
state.counter++;
}
}
通过mutation
更新:
// 这里不一定方法名要交increment(),只是commit()里要是'increment'
methods: {
increment() {
this.$store.commit('increment');
}
}
Mutations传递参数
在通过mutations
更新数据的时候,有可能我们希望携带一些额外的参数
- 参数被称为是
mutations
的载荷(Payload)
比如我们要定义一个方法,使counter
能增加任意数目:
mutations
中的代码:
mutations: {
increment(state, n) {
state.counter += n;
}
}
界面中调用:
methods: {
// increment()方法使counter+2
increment() {
this.$store.commit('increment', 2)
}
}
如果我们有很多参数需要传递:
- 通常我们会以对象的形式传递,也就是
payload
是一个对象 - 再从对象中取出相关的信息
比如我们要添加一个student
对象到state
里的students
数组中:
mutation
中的代码:
mutations: {
addStudent(state, newStudent) {
state.students.push(newStudent);
}
}
这个newStudent
是需要我们传入的一个参数
methods: {
addStudent() {
const newStudent = {id: 115, name: 'new student', age: 20};
this.$store.commit('addStudent', newStudent);
// 直接传也可以
this.$store.commit('addStudent', {id: 115, name: 'new student', age: 20});
}
}
Mutations提交风格
上面例子通过commit
进行提交是一种普通的方式
Vue还提供了另外一种风格,它是一个包含type
属性的对象
this.$store.commit({
type: 'changeCount',
count: 100
})
mutations
中的处理方式是将整个commit
的对象作为payload
使用
changeCount(state, payload){
// payload是commit的对象,要从中取出count
state.count = payload.count;
}
Mutation响应规则
Vuex
的store
中的state
是响应式的,当state
中的数据发生改变时,Vue组件会自动更新
这就要求我们必须遵守一些Vuex
对应的规则:
- 提前在
store
中初始化好所需的属性 - 当给
state
中的对象添加新属性时,使用下面的方式:- 方式一:使用
Vue.set(obj, 'newProp', newValue);
- 方式二:用新对象给旧对象重新赋值**
- 方式一:使用
我们来看一个例子:
<template>
<div id="app">
<p>我的个人信息:{{$store.state.info}}</p>
<button @click="updateInfo">更新信息</button>
</div>
</template>
<script>
export default {
methods: {
updateInfo() {
this.$store.commit('undateInfo');
}
}
}
</script>
const store = new Vuex.Store({
state: {
info: {
name: 'AIpoem',
age: 19
}
},
mutations: {
updateInfo(state) {
state.info['address'] = '12345'
}
}
})
点击更新信息按钮,但界面未发生改变
但Devtools
是能成功跟踪到的,此时info
对象中已经添加了address
属性
说明这种添加属性的方法并不是响应式的
如何才能让state
中添加的属性是响应式的:
- 方式一:使用
Vue.set(obj, 'newProp', newValue);
mutations: {
updateInfo(state) {
Vue.set(state.info, 'address', '12345');
}
}
- 方式二:用新对象给旧对象重新赋值
mutations: {
updateInfo(state) {
state.info = {...state.info, 'address': '12345'}
}
}
⚠️:如果是修改:已经在store
中初始化好的state
属性,是可以直接改的,因为它们已经被添加到vue的响应式系统里了
比如上面的info
对象中的name
属性或是age
属性
使用常量代替Mutations事件类型
把这些常量放在单独的文件中可以让你的代码合作者对整个 app 包含的 mutation 一目了然
用不用常量取决于你——在需要多人协作的大型项目中,这会很有帮助。
在store文件下新建一个mutation-types.js
文件
将原本是'increment'
的地方替换成常量INCREMENT
,要记得导入mutation-types.js
文件
// mutation-types.js
export const INCREMENT = 'increment'
// App.vue
import {INCREMENT} from './store/mutation-types'
methods: {
increment() {
this.$store.commit(INCREMENT)
}
}
// index.js
import {INCREMENT} from './store/mutation-types'
mutations: {
[INCREMENT] (state) {
//...
}
}
Actions
Mutations同步函数
通常情况下,Vuex
要求我们Mutations
中的方法必须是同步方法
- 主要原因是当我们使用
Devtools
时,Devtools
可以帮助我们捕捉mutations
的快照 - 但如果是异步操作,那么
Devtools
将不能很好的跟踪这个操作什么时候会被完成 - 所以通常情况下,不要在
mutations
中进行异步操作
Actions 的基本定义
我们强调,不要在mutations
中进行异步操作
- 某些情况下,如果确实希望在
Vuex
中进行一些异步操作,比如网络请求 - 这时候用
actions
来代替mutations
来进行异步操作
举例:
先尝试在mutations
中进行异步操作,看看结果
state: {
info: {
name: 'AIpoem',
age: 19
}
}
// 错误做法
mutations: {
updateInfo(state) {
// setTimeout模拟异步操作
setTimeout(() => {
state.info.name = '123'
},1000)
}
点了更新信息后,界面已更改
但Devtool
中并未捕捉到
正确使用:
应该先由组件dispatch
给actions
,再由actions
commit
给mutations
,从而修改state
先注册一个action
:
mutations: {
updateInfo(state) {
state.info.name = '123'
}
},
actions: {
aUpdateInfo(context) {
setTimeout(() => {
context.commit('updateInfo');
},1000)
}
},
context
是一个和store
实例具有相同方法和属性的对象,因此可以调用context.commit
提交一个mutation
,但context
并不是store
实例本身
分发action
:
methods: {
updateInfo() {
this.$store.dispatch('aUpdateInfo');
}
}
这个时候再点击更新信息,界面发生更改
同时Devtool
也能记录到
Actions传递参数
actions
同样也能传递参数:
actions: {
// payload参数
aUpdateInfo(context, payload) {
setTimeout(() => {
context.commit('updateInfo');
},1000)
}
},
methods: {
updateInfo() {
this.$store.dispatch('aUpdateInfo', '我是payload');
}
}
如果想传递多个函数,也和mutations
一样,传一个对象到payload
,再从对象里取出来信息
我们还可以定义一个成功的回调,
methods: {
updateInfo() {
this.$store.dispatch('aUpdateInfo', {
message: '一些携带的信息',
success() {
console.log('已经完成了');
}
};
}
}
actions: {
aUpdateInfo(context, payload) {
setTimeout(() => {
context.commit('updateInfo');
// 执行到这里已经成功commit
payload.success();
},1000)
}
},
一种更优雅的方式:
(携带信息和回调不放在一起)
遇到resolve()
就会去执行then()
里的内容,并且将整个promise
对象返回给调用的方法
可以理解成this.$store.dispatch('aUpdateInfo', '我是payload')
被替换为 new Promise(...)
,然后继续执行then()
modules
由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store
对象就有可能变得相当臃肿。
- 为了解决以上问题,
Vuex
允许我们将store
分割成模块 - 每个模块拥有自己的
state
、mutations
、actions
、getters
、甚至是嵌套子模块——从上至下进行同样方式的分割:
const moduleA = {
state: {
name: 'AIpoem'
},
mutations: { ... },
actions: { ... },
getters: { ... }
}
const store = new Vuex.Store({
modules: {
a: moduleA
}
})
<h2>{{ $store.state.a.name }}</h2>
上面的代码中,我们已经有了整体的组织结构,下面我们来看看具体的局部模块中代码
mutations和getters
const moduleA = {
state: {
count: 0
},
mutations: {
increment(state){
state.count++;
}
},
getters: {
doubleCount(state) {
return state.count * 2
}
}
}
<h2>{{ count }}</h2>
<button @click="increment"></button>
computed: {
count() {
return this.$store.getters.doubleCount;
}
}
methods: {
increments() {
this.$store.commit('increment');
}
}
mutations
和getters
接收的第一个参数是局部状态对象
⚠️:虽然我们的 doubleCount
和 increment
都是定义在对象内部的
但是在调用的时候,依然是通过this.$store
来调用的
actions
接收一个context
参数对象
- 局部状态通过
context.state
暴露出来,根节点状态则为context.rootState
actions: {
// state、commit、rootState是context对象的属性,这里是对象解构写法
incrementIfOddOnRootSum ({ state, commit, rootState }) {
if ((state.count + rootState.count) % 2 === 1) {
commit('increment')
}
}
}
⚠️:如果getters
也需要使用全局的状态,也可以接收rootState
参数
2. 项目结构
Vuex
并不限制你的代码结构。
但是,它规定了一些需要遵守的规则:
- 应用层级的状态应该集中到单个
store
对象中。 - 提交
mutations
是更改状态的唯一方法,并且这个过程是同步的。 - 异步逻辑都应该封装到
actions
里面。
只要你遵守以上规则,如何组织代码随你便。
如果你的 store 文件太大,只需将 actions
、mutations
和 getters
分割到单独的文件。
对于大型应用,我们会希望把 Vuex
相关代码分割到模块中。下面是项目结构示例:
├── index.html
├── main.js
├── api
│ └── ... # 抽取出API请求
├── components
│ ├── App.vue
│ └── ...
└── store
├── index.js # 我们组装模块并导出 store 的地方
├── actions.js # 根级别的 action
├── mutations.js # 根级别的 mutation
└── modules
├── cart.js # 购物车模块
└── products.js # 产品模块
就是将action
、mutations
、 getters
都抽出去,
state
不用抽