状态管理模式包含以下三个部分:
state,驱动应用的数据源
view,以声明方式将 state 映射到视图
actions,响应在 view 上的用户输入导致的状态变化
store:
Vuex 核心 store 是一个容器,包含应用中的状态 (state)
Vuex 的状态是响应式的,组件从 store 中读取状态时,状态(state)发生变化时相应的组件会更新
不能直接改变 state!改变 state 的唯一途径是显式提交 (store.commit) mutation
根实例注册 store 选项: 通过 this.$store 访问
const Counter = {
template: `<div>{{ count }}</div>`,
computed: {
count () { return this.$store }
}
}
1、创建 Store
const store = new Vuex.Store({
state: { count: 0 }
})
2、通过 this.$store.state 来获取状态对象
console.log( this.$store.state.count )
State:单一状态树,Vuex 中用对象(state)包含全部的应用状态作为"唯一数据源
API: state
type: Object | Function
Vuex store 实例的根 state 对象
如果传入返回一个对象的函数,其返回的对象会被用作根 state
组件中获得 Vuex 状态(state): 从 store 实例中读取状态,在计算属性( computed )中返回
const app = new Vue( {
el: '#app',
store,
components: { Counter },
template: '<Counter />'
} );
const Counter = {
template: `<div>{{ count }}</div>`,
computed: {
count () {
return this.$store.state.count /* 在计算属性中获取 VueX 中的状态 state */
}
}
}
Getter
组件保有局部状态,接受 state 作为其第一个参数,在组件的计算属性中获取( computed )
Vuex 允许在 store 中定义"getter"( store 的计算属性)
像计算属性一样,getter 返回值会根据依赖被缓存,只有依赖值改变才会重新计算
API: getters
type: { [key: string]: Function }
在 store 上注册 getter,getter 方法接受以下参数:
state, // 如果在模块中定义则为模块的局部状态
getters, // 等同于 store.getters
当定义在一个模块里时会特别一些:
state, // 如果在模块中定义则为模块的局部状态
getters, // 等同于 store.getters
rootState // 等同于 store.state
rootGetters // 所有 getters
Getter 通过属性访问: store.getters 对象
const store = new Vuex.Store({
state: {
todos: [{ id: 1, text: 'hello', done: true }]
},
getters: {
doneTodos: state => {
return state.todos.filter(todo => todo.done)
}
}
})
store.getters.doneTodos // [{ id: 1, text: '...', done: true }]
Getter 可以接受其他 getter 作为第二个参数
getters: {
doneTodosCount: ( state, getters ) => {
return getters.doneTodos.length
}
}
store.getters.doneTodosCount // 1
任何组件中使用Getter: 通过属性访问时作为 Vue 的响应式的一部分缓存其中
computed: { /* 计算属性中获取 getters */
doneTodosCount () {
return this.$store.getters.doneTodosCount
}
}
通过方法访问Getter: 返回函数给 getter 传参且通过方法访问每次都会调用而不会缓存结果
getters: {
getTodoById: ( state ) => ( id ) => {
return state.todos.find( todo => todo.id === id )
}
}
/* 在对 store 里的数组查询时有用 */
store.getters.getTodoById( 2 ) // { id: 2, text: '...', done: false }
Mutation 需遵守 Vue 的响应规则
1、Vuex 的 store 中的状态是响应式的,当变更状态时,监视状态的 Vue 组件也会自动更新
2、提前在 store 中初始化好所有所需属性
API: mutations
type: { [type: string]: Function }
在 store 上注册 mutation,处理函数接受 state 为第一个参数,payload 为第二个参数(可选)
Mutation
更改 store 状态唯一方法是通过 store.commit 方法提交 mutation
mutation 类似事件,每个 mutation 有字符串的事件类型( type )和回调函数 ( handler )
回调函数就是进行状态更改的地方,并且它会接受 state 作为第一个参数
const store = new Vuex.Store({
state: { count: 1 },
mutations: {
increment (state) { /* 当触发类型为 increment 的 mutation 时,调用此函数 */
state.count++
}
}
});
store.commit('increment'); /* 提交 mutation */
Payload: 向 store.commit 传入额外的参数,即 mutation 的载荷
store.commit( { type: 'increment', amount: 10 } );
mutations: {
increment (state, payload) { /* Object: 接收 store.commit() 传递的参数 */
state.count += payload.amount
}
}
Mutation 必须是同步函数:
当 mutation 触发时回调函数还没调用(不确定什么时候被调用)会导致状态不可控
mutations: {
someMutation ( state ) {
api.callAsyncMethod(() => { /* 回调函数不确定什么时候被调用导致状态不可控 */
state.count++
})
}
}
常量替代 Mutation 事件类型
/* mutationType.js */
export const SOME_MUTATION = 'some_mutation';
/* store.js */
import Vuex from 'vuex'
import { SOME_MUTATION } from './mutationTypes'
mutations: {
[SOME_MUTATION] (state) { } /* 常量代替事件对象 */
}
Action
Action 提交的是 mutation,而不是直接变更状态
Action 可以包含任意异步操作
API: actions
type: { [type: string]: Function }
在 store 上注册 action。处理函数接受 context 作为第一个参数,payload 作为第二个参数(可选)
context 对象包含以下属性:
{
state, // 等同于 `store.state`,若在模块中则为局部状态
rootState, // 等同于 `store.state`,只存在于模块中
commit, // 等同于 `store.commit`
dispatch, // 等同于 `store.dispatch`
getters, // 等同于 `store.getters`
rootGetters // 等同于 `store.getters`,只存在于模块中
}
context: 接收和 store 实例相同方法和属性的 context 对象
调用 context.commit 提交一个 mutation
通过 context.state 和 context.getters 来获取 state 和 getters
const store = new Vuex.Store({
state: { count: 0 },
mutations: {
increment (state) {
state.count++
}
},
actions: {
increment ( context ) {
context.commit('increment')
}
/* 等价于 */
increment ({ commit }) { /* 解构赋值 */
commit('increment')
}
}
})
分发 Action: 通过 store.dispatch 方法触发在 action 内部执行异步操作
Actions 支持载荷方式和对象方式进行分发
/* 以载荷形式分发 */
store.dispatch('incrementAsync', { amount: 10 } );
/* 以对象形式分发 */
store.dispatch( { type: 'incrementAsync', amount: 10 } )
组件 methods 中分发 Action: this.$store.dispatch()
methods: {
addActions(){
/* 载荷分发 */
this.$store.dispatch( 'actionName', { a: 1 } );
/* 对象分发 */
this.$store.dispatch({ type: 'actionName', a: 1})
}
}
API: mapState
mapState(namespace?: string, map: Array | Object<string | function>): Object
为组件创建计算属性以返回 Vuex store 中的状态
第一个参数是可选的,可以是一个命名空间字符串
对象形式的第二个参数的成员可以是一个函数。function(state: any)
mapState 辅助函数
将 store 中的 state 状态映射到局部计算属性 computed
mapState 函数返回的是一个对象
单独构建的版本中辅助函数为 Vuex.mapState
import { mapState } from 'vuex';
computed: mapState({
count: state => state.count, /* 箭头函数传参 */
countAlias: 'count', /* 传字符串参数 'count' 等同于 state => state.count */
countState ( state ) { /* 为了能够使用 `this` 获取局部状态,必须使用常规函数 */
return state.count + this.localCount
}
})
映射计算属性的名称与 state 的属性名相同时可以传一个字符串数组
computed: mapState( [ 'count' ] ); /* 映射 this.count 为 store.state.count */
对象展开运算符: mapState 函数返回的是一个对象
computed: {
increment(){},
...mapState({}) /* 返回一个对象 */
}
API: mapGetters
mapGetters(namespace?: string, map: Array | Object): Object
为组件创建计算属性以返回 getter 的返回值
第一个参数是可选的,可以是一个命名空间字符串
mapGetters 辅助函数
将 store 中的 getter 映射到局部计算属性 computed
/* 数组形式 */
import { mapGetters } from 'vuex';
computed: {
...mapGetters( [ 'doneTodos' ] )
}
/* 对象形式 */
mapGetters( { doneCount: 'doneTodosCount' } );
API: mapMutations
mapMutations(namespace?: string, map: Array | Object<string | function>): Object
创建组件方法提交 mutation
第一个参数是可选的,可以是一个命名空间字符串
对象形式的第二个参数的成员可以是一个函数。function(commit: function, …args: any[])
mapMutations 辅助函数
将组件中的 methods 映射为 store.commit 调用
<div @click="incrementBy( 'hello' )"></div> /**/
import { mapMutations } from 'vuex'
export default {
methods: {
handleChangeCity( city ){
/* this.$store.commit( { type: 'changeCity', value: city } ); */
this.changeCity( city ); /* 提交的事件类型会被 vuex 自动转为函数 */
this.$router.push( { path: '/' } );
},
...mapMutations([ /* 数组带参数形式传递 */
'changeCity'
])
...mapMutations([ /* 数组形式 */
/* this.increment() 映射为 this.$store.commit('increment') */
'increment',
/*载荷形式:this.incrementBy(amount) 映射为 this.$store.commit('incrementBy', amount)*/
'incrementBy'
]),
...mapMutations({ /* 对象形式 */
add: 'increment' /* this.add() 映射为 this.$store.commit('increment') */
})
}
}
API: mapActions
mapActions(namespace?: string, map: Array | Object<string | function>): Object
创建组件方法分发 action
第一个参数是可选的,可以是一个命名空间字符串
对象形式的第二个参数的成员可以是一个函数。function(dispatch: function, …args: any[])
mapActions 辅助函数
将组件的 methods 映射为 store.dispatch 调用
import { mapActions } from 'vuex'
export default {
methods: {
...mapActions([
'increment', // 将 `this.increment()` 映射为 `this.$store.dispatch('increment')`
// `mapActions` 也支持载荷:
// `this.incrementBy(amount)` 映射为 `this.$store.dispatch('incrementBy', amount)`
'incrementBy'
]),
...mapActions({
add: 'increment' // 将 `this.add()` 映射为 `this.$store.dispatch('increment')`
})
}
}
Module
Vuex 允许将 store 分割成模块(module)
每个模块拥有自己的 state、mutation、action、getter
const moduleA = {
state: { ... },
mutations: { ... },
actions: { ... },
getters: { ... }
}
const moduleB = {
state: { ... },
mutations: { ... },
actions: { ... }
}
const store = new Vuex.Store({
modules: {
a: moduleA,
b: moduleB
}
})
store.state.a // -> moduleA 的状态
store.state.b // -> moduleB 的状态
模块的局部状态
对于模块内部的 mutation 和 getter,接收的第一个参数是模块的局部状态对象。
const moduleA = {
state: { count: 0 },
mutations: {
increment (state) {
// 这里的 `state` 对象是模块的局部状态
state.count++
}
},
getters: {
doubleCount (state) {
return state.count * 2
}
}
}
对于模块内部的 action,局部状态通过 context.state 暴露出来,根节点状态则为context.rootState:
const moduleA = {
// ...
actions: {
incrementIfOddOnRootSum ({ state, commit, rootState }) {
if ((state.count + rootState.count) % 2 === 1) {
commit('increment')
}
}
}
}
对于模块内部的 getter,根节点状态会作为第三个参数暴露出来:
const moduleA = {
// ...
getters: {
sumWithRootCount (state, getters, rootState) {
return state.count + rootState.count
}
}
}
项目结构
应用层级的状态应该集中到单个 store 对象中
提交 mutation 是更改状态的唯一方法,并且这个过程是同步的
异步逻辑都应该封装到 action 里面
├── index.html
├── main.js
├── api
│ └── ... # 抽取出API请求
├── components
│ ├── App.vue
│ └── ...
└── store
├── index.js # 我们组装模块并导出 store 的地方
├── actions.js # 根级别的 action
├── mutations.js # 根级别的 mutation
└── modules
├── cart.js # 购物车模块
└── products.js # 产品模块
表单处理: 给 中绑定 value,然后侦听 input 或者 change 事件,在事件回调中调用 action