抛砖引玉
上图中Header、Slider和Bottom属于同层级的的组件,Content属于路由组件。
Header、Slider和Bottom可以通过Props、自定义事件和provide/inject实现组件间的通信,但是Content与以上三个组件没有直接关联关系,它们之间的相互通信是隔绝,我们可以使用provide/inject实现路由子组件到父组件的通信,但是父组件到路由子组件的通信就做不到了。
那么,我们就需要一个路由子组件与页面父组件和兄弟组件的沟通桥梁,如此,Vuex就很好的解决了我们这个问题。
Vuex的介绍
Vuex,状态管理方式,专门为Vue应用程序开发的工具,采用集中式存储管理应用的所有组件的状态,很好的解决了所有组件(路由子组件与页面父组件和兄弟组件)之间的相互通信问题。
它就相当于是所有组件的一个大型、统一管理的数据仓库,管理着所有组件的共享状态,它们都可以从这里获取数据或触发行为,同时其它相应组件也可以实时监听到数据的变更。
特点:
1)Vuex应用的核心是store(仓库),每个应用只有一个 store 实例
2)Vuex的状态存储是响应式的,当组件从store读取状态时,其他组件更新了状态值,那么相应组件读到的状态也会得到高效更新。简单讲,就是state被更新,但凡是读取了state的组件都会得到实时更新。
3)为了方便我们跟踪每一个状态的变化,不能直接修改store中的状态,只能显式提交(commit)mutation来修改。示例:this.store.commit("updatedStateMethod", "这是更新值");
Vuex的核心要素
Vuex有5大核心要要素:State、Getters、Mutation、Actions、Modules。
1. State
单一树状态,用来注册(初始化)共享状态,作用类似于组件中的data。
注册(初始化)方式
export default {
state: {
todoList: [],
count: 0,
},
}
组件内访问方式:
// ...
export default {
// ...
// 监听
watch: {
"$store.state.message.todoList": {
deep: true,
handler(val) {
console.log(val);
},
},
"$store.state.message.count"(val) {
console.log(val);
}
},
// 计算属性
computed: {
// 直接访问
count() {
return this.$store.state.message.count;
},
// mapState辅助函数
...mapState({
"todoList",
// 箭头函数可使代码更简练
count: state => state.count,
// 传字符串参数 'count' 等同于 `state => state.count`
countAlias: 'count',
// 为了能够使用 `this` 获取局部状态,必须使用常规函数
countPlusLocalState (state) {
return state.count + this.localCount
}
})
},
// 方法
methods: {
func() {
this.todoList = this.$store.state.message.todoList;
},
}
}
2. Getters
用来计算共享状态的属性,作用类似于组件中的计算属性computed,可以直接理解是store的computed。
例如:在多个组件内都使用到了如下方法,我们无非就是复制粘贴或提取为一个共享方法然后多处导入,但是两个都不是最理想的。
computed: {
doneTodosCount () {
return this.$store.state.todos.filter(todo => todo.done).length
}
}
Vuex提供的getters就很好的解决了这个问题。就像计算属性一样,getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。
示例:
const store = new Vuex.Store({
state: {
todos: [
{ id: 1, text: '...', done: true },
{ id: 2, text: '...', done: false }
]
},
getters: {
// Getter 接受 state 作为其第一个参数
doneTodos: state => {
return state.todos.filter(todo => todo.done)
},
// Getter 也可以接受其他 getter 作为第二个参数
doneTodosCount: (state, getters) => {
return getters.doneTodos.length
},
// 也可以通过返回一个函数,来实现给getters传参(使用的时候可以缩写,我这为了看起来明了就没有缩写)
getTodoById: (state) => {
return (id) => {
return state.todos.find(todo => todo.id === id)
}
}
}
})
在组件内部我们可以通过属性、方法和mapGetters辅助函数三种方式获取getters的计算。
通过属性访问:
this.$store.getters.doneTodosthis.$store.getters.doneTodosCount
通过方法访问:
this.$store.getters.getTodoById(2)
注意,getter 在通过方法访问时,每次都会去进行调用,而不会缓存结果。
mapGetters辅助函数: 使用对象展开运算符将 getter 混入 computed 对象中:
import { mapGetters } from 'vuex'
export default {
// ...
computed: {
...mapGetters([
'doneTodosCount',
'anotherGetter',
// ...
]),
}
}
想将一个 getter 属性另取一个名字,使用对象形式:
import { mapGetters } from 'vuex'
export default {
// ...
computed: {
...mapGetters({
// 把 `this.doneCount` 映射为 `this.$store.getters.doneTodosCount`
doneCount: 'doneTodosCount'
})
}
}
3. Mutation
用来修改数据。
修改store中的状态的唯一方法是提交 Mutation 。
原则:必须是同步函数
Mutation的注册方式和传参:
const store = new Vuex.Store({
state: {
count: 1
},
mutations: {
// 会接受state作为第一个参数
increment (state) {
// 变更状态
state.count++
},
// 传入额外的参数,即 mutation 的 载荷(payload)。
increment1 (state, n) {
state.count += n;
},
// 载荷(payload)的字符类型可以是字符串、数字、对象等,具体可以根据实际情况
increment2 (state, payload) {
state.count += payload.num;
},
//
increment3 (state, payload) {
state.count += payload.num;
},
}
})
Mutation的提交方式:
// 在组件内部用mapMutations提交时
import { mapMutations } from 'vuex'
export default {
// ...
methods: {
// 直接提交
userCommit() {
this.$store.commit('increment'); // 不传参
this.$store.commit('increment1', 10); // 传参类型是数字
this.$store.commit('increment2', { num: 10 }); // 传参类型是对象
this.$store.commit({ type: "increment", num: 10 }); // 使用对象风格方式提交
},
// 使用mapMutations提交
...mapMutations([
'increment', // 将 `this.increment()` 映射为 `this.$store.commit('increment')`
// `mapMutations` 也支持载荷:
'increment3' // 将 `this.increment3(amount)` 映射为 `this.$store.commit('increment3', amount)`
]),
...mapMutations({
add: 'increment' // 将 `this.add()` 映射为 `this.$store.commit('increment')`
})
}
}
// 在定义store文件中提交时
store.commit('increment'); // 不传参
store.commit('increment1', 10); // 传参类型是数字
store.commit('increment2', { num: 10 }); // 传参类型是对象
store.commit({ type: "increment", num: 10 }); // 使用对象风格方式提交
4. Actions
Actions类似于mutation,区别在于:
- Actions提交的是mutation,而不是直接变更状态;
- Actions可以包含任意异步操作;
Action 函数接受一个与 store 实例具有相同方法和属性的 context 对象,因此你可以调用 context.commit 提交一个 mutation,或者通过 context.state 和 context.getters 来获取 state 和 getters。
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment (state) {
state.count++
},
incrementPayload (state, data) {
state.count += data.num;
},
},
actions: {
// 简单使用
increment (context) {
context.commit('increment')
},
// 使用到ES6的参数解构来简化代码
incrementES6 ({ commit }) {
commit('incrementES6')
},
// 可以执行异步操作
incrementAsync ({ commit }) {
setTimeout(() => {
commit('increment')
}, 1000)
},
// 载荷
incrementPayload({ commit }, data) {
setTimeout(() => {
commit('increment', data)
}, 1000)
},
}
})
分发(触发)Action:
export default {
// ...
methods: {
useActionsFun() {
// 简单使用
this.$store.dispatch('increment');
// 对象方式
this.$store.dispatch({ type: "incrementES6" });
// 载荷形式
this.$store.dispatch('incrementPayload', { num: 10 });
},
}
}
5. Modules
模块拆分。为了解决应用变得非常复杂时,store 对象就有可能变得相当臃肿的问题。
每个模块拥有自己的state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割:
// index.js
import Vue from 'vue'
import Vuex from 'vuex'
import app from './modules/app'
Vue.use(Vuex)
let store = new Vuex.Store({
// 1. state
state: {},
// 2. getters
getters: {},
// 3. actions
// 通常跟api接口打交道
actions: {},
// 4. mutations,通常用于同步的,更新state数据
mutations: {},
modules: {
app,
}
})
export default store
// app.js
export default {
state: {},
getters: {},
actions:{},
mutations: {},
}
Vuex的学习资料
-
官方资料:
-
优秀博客: