Vuex就是一个保存、管理共享数据的容器。没有Vuex之前,组件之间数据共享需要通过props与$emit,有了Vuex后组件之间共享数据更便于调试。
如果不打算开发大型单页应用,使用 Vuex 可能是繁琐冗余的,简单应用自己使用一个store模式就足够了。Vuex更适合构建一个中大型单页应用
Vuex 使用单一状态树。就是说每个应用将仅仅包含一个 store 实例,store实例从根组件“注入”到每一个子组件中,所有子组件都能使用this.$store
访问到。
1、state:
响应式的,state数据变化,组件就会调用render重新刷新。需要将获取state的操作放入computed中,这样当state中的数据改变时,computed会重新计算并且缓存。
state可通过this.$store.state
来访问。
Vue响应式原理:Vue 将遍历 Vue 实例的 data 对象中的所有的属性并使用 Object.defineProperty 把这些属性全部转为 getter/setter,当setter方法被调用时,将会通知watcher,然后调用render重新刷新对应组件。
- mapState:当一个组件需要获取多个状态(共享数据)时,可以使用mapState辅助函数,避免写太多计算属性。
mapState(Object/Array)
2、getter:
//getters直接定义在store中
getters: {
/**
* state:状态(共享数据)
* getters:相当于this.$store.getters
*/
param1: (state, getters) => {...}
}
//getters定义在模块中
getters: {
/**
* state:局部状态(模块内的共享数据)
* getters:局部getters(模块内的getters)
* rootState:根状态(根共享数据)
* rootGetters:根getters
*/
param1: (state, getters, rootState, rootGetters) => {...}
}
当需要从 store 中的 state 中派生出一些状态,例如对列表进行过滤并计数时,就需要在组件的computed中做操作:
computed: {
doneTodosCount () {
return this.$store.state.todos.filter(todo => todo.done).length
}
}
假设多个组件需要用到此属性,我们要么复制这个函数,或者抽取到一个共享函数然后在多处导入它——无论哪种方式都不是很理想
而getter正好解决了这问题,与Vue的计算属性类似,getter相当于store的计算属性。
getter可通过this.$store.getters
访问。有两种方式访问:一种时访问属性,会缓存;另一种时访问方法,不会缓存
- mapGetters 辅助函数:与mapState类似,都是写在组件的computed属性中来访问共享数据。mapGetters里边的属性,都用来映射获取共享数据getters中对应的方法。
3、Mutation
//mutations直接定义在store中的方式。
mutations: {
/**
* state: 状态(定义的所有的共享数据)
* payload: 载荷(而外参数,一般为一个对象)
*/
param1: (state, payload) => {...}
}
//mutations定义在Module中的方式。
mutations: {
/**
* state: 局部状态(模块内的共享数据)
* payload: 载荷(而外参数,一般为一个对象)
*/
param1: (state, payload) => {...}
}
在多人合作的项目中,我们可以使用ES2015 风格的计算属性命名功能来使用一个常量作为函数名:
// mutation-types.js
export const SOME_MUTATION = 'SOME_MUTATION'
// store.js
import Vuex from 'vuex'
import { SOME_MUTATION } from './mutation-types'
const store = new Vuex.Store({
state: { ... },
mutations: {
// 我们可以使用 ES2015 风格的计算属性命名功能来使用一个常量作为函数名
[SOME_MUTATION] (state) {
// mutate state
}
}
})
Vuex中更改store状态的唯一方法是通过提交Mutation:
this.$store.commit('param1', {
amount: 10
})
或者对象风格提交方式,整个对象都作为载荷传给 mutation 函数
this.$store.commit({
type: 'param1',
amount: 10
})
模块内使用需要:this.$store.commit('ModuleA/getAllProducts')
- mapMutations 辅助函数将组件中的 methods 映射为 store.commit 调用
4、Action
actions: {
/*
* context: 与 store 实例具有相同方法和属性的对象,但是不是store实例本身
* 注意这里的context可以采用解构赋值。
* 在Module中context同样也包含了rootState对象。
* payload:载荷(额外参数)
* return:不一定要有return,只有在需要组合Action时才需要用到。因为dispatch函数可以返回一个Promise对象。
*/
param1: (context, payload) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
commit('someMutation')
resolve()
}, 1000)
})
}
}
Action主要用来弥补mutation无法进行异步操作的缺陷,Action无法直接修改数据,同样也是提交mutation,只是把提交mutation的操作放到了异步回调中。
Action 通过 this.$store.dispatch
方法触发
模块内使用需要:this.$store.dispatch('ModuleA/getAllProducts')
- mapActions 同样将内部定义属性映射到this.$store.dispatch(...);
5、Module
基本流程:
/*----------------------moduleA.js------------------------*/
const state = {...}
const mutations = {...}
const getters = {...}
const actions = {...}
export default {
namespace: true, //这个还没太理解
state,
getters,
actions,
mutations
}
/*----------------------moduleB.js------------------------*/
const state = {...}
const mutations = {...}
const getters = {...}
const actions = {...}
export default {
namespace: true,
state,
getters,
actions,
mutations
}
/*----------------------store.js------------------------*/
import Vue from 'vue'
import Vuex from 'vuex'
import cart from './moduleA.js'
import products from './moduleB.js'
Vue.use(Vuex)
export default new Vuex.Store({
modules: {
moduleA,
moduleB
}
})
/*----------------------app.js------------------------*/
import store from './store.js'
new Vue({
el: '#app',
store,
render: h => h(App)
})
/*----------------------ComponentA.vue------------------------*/
注意在模块中我们可以通过参数
rootState.模块名.state属性名
拿到其他模块的状态。
问题1:namespace: true
默认情况下,模块内部的 action、mutation 和 getter 是注册在全局命名空间的,所以在调用这些对象内的方法时不需要指定模块路径,如果加上namespace: true那么在组件的methods中映射store.dispatch()时需要这样做:
methods:{
...mapActions([
'moduleA/action1', //相当于this.$store.dispatch('moduleA/action1', {...})
'moduleB/action2'
])
}
可以简写为:
methods: {
...mapAction('moduleA', [
'action1',
'action2'
])
}
问题2:异步修改state时,为什么要将异步操作写在Action中,为什么不直接在回调提交mutation?
与getter相似。实际上直接在异步回调函数中提交mutation也是可行的,但是异步操作无法复用,如果几个组件都要用到这个异步操作,那就得在每个组件中去写一遍,太过于繁琐。
注意:这里说的是在回调函数种提交mutation,而不是在mutation中写异步回调,mutation中必须是同步事物。异步会使程序devtools很难调试,因为如果有多个异步回调去改变状态,devtools无法确定哪个先执行。
问题3:Action的第一个参数context为什么不是store实例本身?
因为在Module中context对象包含了局部state与rootState,如果是store实例本身怎么会有局部state呢!
问题4:Vue响应式原理?
Vue响应式原理:Vue 将遍历 Vue 实例的 data 对象中的所有的属性并使用 Object.defineProperty 把这些属性全部转为 getter/setter,当setter方法被调用时,将会通知watcher,然后调用render重新刷新对应组件。
网友评论