2018/8/18 vuex
Vuex---快速上手
本文主要是基于Vuex官方文档https://vuex.vuejs.org/zh/guide. 主要是对Vuex进行简单的介绍和使用说明.没有涉及到探析源码的深度,目的只是让刚接触Vuex的童鞋能在看完本文之后能够对Vuex有一定的了解,以及简单的使用。
安装
NPM
npm install vuex --save
复制代码
Yarn
yarn add vuex
复制代码
注意在模块化开发过程中,必须通过Vue.use()
方法来安装Vuex;之后可以在组件中使用this.$store拿到这个Store对象
import Vue from "vue"
import Vuex from "vuex";
Vue.use(Vuex)
复制代码
Vuex到底是个啥?
Vuex是一个类似于Redux的状态管理器,是一个专门为Vue.js框架设计的、用于对Vue.js应用程序进行状态管理的库,将共享的数据抽离到全局,以一种单例模式来存放,同时利用Vue.js的响应式机制来进行高效的状态管理与更新.
核心Store
Vuex应用核心就是Store,是一个全局的仓库,里面存储着我们项目中大部分所需要的状态state,但是vuex与普通的全局对象又有着截然不同的之处. 1)Store中的state是响应式的,当Store中的状态state发生改变的时候,那么响应的组件中的state也会相应的得到高效刷新. 2)Store是单向数据流,改变Store中的状态state只能通过mutations(突变)这个唯一途径。 [如图]
创建一个简单的Vuex示例
import Vue from 'vue'
import Vuex from "vuex"
//导入vuex自带的日志插件
import logger from "vuex/dist/logger";
//导入对应的 mutations,actions getters
import mutations from "./mutations"
import actions from "./actions"
import getters from "./getters"
//安装Vuex
Vue.use(Vuex)
//初始化状态state
const state={
userList:[],
sum:1
}
//通过Vuex.Store来创建一个仓库Store
export default new Vuex.Store({
state,
mutations,
actions,
getters,
strict:true,//是否开启严格模式
plugins:[logger],//导入插件
})
复制代码
注册Store
如果我们想在组件中使用“仓库中的状态”state,我们还需要将实例化的store导入VUE中.
import Vue from 'vue'
import App from './App'
import store from "./store"
new Vue({
el: '#app',
store,//==》store:store;导入实例化store
render: h => h(App)
})
复制代码
通过在根实例中注册 store 选项,该 store 实例会注入到根组件下的所有子组件中,且子组件能通过 this.$store 访问到
Store的各个部分介绍及使用方法
- state
- actions
- mutations
- getters
- mapState 辅助函数
- mapActions 辅助函数
- mapMutations 辅助函数
- mapGetters 辅助函数
- modules
State
官方文档解释的很清楚了,"Vuex 使用单一状态树,用一个对象(state)就包含了全部的应用层级状态。至此它便作为一个“唯一数据源 (SSOT)”而存在''. 比如,仓库中有一个数据:
export defult new Vuex.Store({
state:{
count:1
}
})
复制代码
在组件中想要获取这个状态
export default {
name:son,
computed: {
num(){
return this.$store.state.count
}
}
}
复制代码
所以我们如果想在VUE组件中获取到store的展示状态,最简单的方法就是通过计算属性computed
返回这个状态,这样每当 store.state.count 变化的时候, 都会重新求取计算属性,并且触发更新相关联的 DOM
mapState辅助函数
是vuex中封装成型的辅助函数,mapState 函数返回的是一个对象 这个辅助函数主要为了让我们在开发过程中减少大量的重复和冗余声明过程,它会帮助我们快速生成计算属性。
import {mapState} from "vuex"
export default {
// ...
computed:{
...mapState({
//箭头函数的方式 this.num = this.$store.state.count
num:state=>state.count ,
//传入字符串的形式 等同于 this.num1 = this.$store.state.count
num1:'count',
//如果想实现当前组件局部数据与state数据相结合,就必须使用常规函数了,
num2(state){
return state.count+this.a
}
},
)
}
}
复制代码
当我们想要映射的计算属性的名称和state中属性名相同时,我们可以给mapState传入字符串数组。每个字符串代表着state的对应的值
computed: mapState([
// 映射 this.count 为 store.state.count
'count'
])
复制代码
Getters
store中的getters就类似于vue中的计算属性computed
,当state发生改变时,高效的更新自己的返回状态
export defult new Vuex.Store({
state: {
todos: [1,3,5,7,2,4,6]
},
getters: {
//简单的将state.todos这个数组过滤
doneTodos: state => {
return state.todos.filter(todo => todo%2===0 )
}
},
//第二参数可以是其他getters
dolen:(state,getters)=>{
//返回getters中doneTodos长度
return getters.doneTodos.length
},
//我们可以返回一个函数,实现给getter传参
model:(state,getters)=>n=>getters.doneTodes.fifter(item=>item>n)
})
复制代码
在组件中怎么使用getters呢?
当在store中注入getters对象时,我们便可以通过this.$store.getters
这个对象来访问一些值;
this.$store.getters.delen;
this.$store.getters.model(3);
...
复制代码
当然最常用的方式还是放在组件的computed
计算属性中
computed: {
delen() {
return this.$store.getters.delen
}
}
复制代码
mapGetters
如同mapState
辅助函数一样,也是快速帮我们将getters映射到计算属性
当getters的每一项的名字和计算属性的名字一致时,我们可以放入一个数组,每一项是对象的getters中的每一项的名字
import { mapGetters } from 'vuex'
export default {
// ...
computed: {
// 使用对象展开运算符将 getter 混入 computed 对象中
...mapGetters([
//等价于this.dolen=this.$store.getters.dolen
'dolen',
//等价于this.dolen=this.$store.getters.model
'model'
])
}
}
复制代码
当你想给一个getter另起一个名字的时候,可以这样写
mapGetters({
// 把 `this.doneCount` 映射为 `this.$store.getters.doneTodosCount`
doneCount: 'doneTodosCount'
})
复制代码
Mutation
Mutation是唯一能更改Vuex中的store中的state状态的方法,每一个mutation都会有一个字符串的事件类型type和一个回调函数handler,这个回调函数就是我们实际进行状态更改的地方,并且它会接受 state 作为第一个参数:
const store = new Vuex.Store({
state: {
count: 1
},
mutations: {
/*increment 就是type
*后面对应的函数就是handler
*/
increment: (state)=> {
// 变更状态
state.count++
}
}
})
复制代码
当然我们一般上述的样式简写成
const store = new Vuex.Store({
state: {
count: 1
},
mutations: {
increment(state) {
// 变更状态
state.count++
}
}
})
复制代码
我们不能直接调用这个handleer,必须通过this.$store.commit
这个方法来提交mutation
commit提交方式
A:this.$store.commit(type,payload)
第一个参数type是字符串类型的事件类型
第二个参数payload是提交载荷,即传给mutation的额外的参数,可以是任意类型(在大多数情况下,载荷应该是一个对象,这样可以包含多个字段并且记录的 mutation 会更易读)
this.$store.commit('increment',10)
const store = new Vuex.Store({
state: {
count: 1
},
mutations: {
increment(state,n) {
// 变更状态
state.count+=n
}
}
})
复制代码
B:this.$store.commit({type,payload})
这是一种对象风格的提交风格
this.$store.commit({
type:'increment',
amount:10
})
const store = new Vuex.Store({
state: {
count: 1
},
mutations: {
increment(state,payload) {
// 变更状态
state.count+=payload.amount
}
}
})
复制代码
组件中使用commit提交Mutation
在组件中使用 this.$store.commit('xxx') 提交 mutation,或者使用 mapMutations 辅助函数将组件中的 methods 映射为 store.commit 调用(需要在根节点注入 store)。
import { mapMutations } from 'vuex'
export default {
// ...
//同样两种方式
//第一种,传入数组,每一项都是字符串类型的Mutation的事件类型,
methods: {
//映射为 this.increment()=this.$store.commit('increment')
...mapMutations(['increment']
}
//第二种,传入对象,将Mutations的mutation令起一个名字
...mapMutations({
//将this.add()映射为this.$store.commit('increment')
add:'increment'
})
}
复制代码
Mutations三点注意
- Mutation 必须是同步函数
因为devtools 都需要捕捉到前一状态和后一状态的快照,如果是异步的话,任何在回调函数中进行的状态的改变都是不可追踪的
2)Mutation 必须遵守 Vue 的响应规则.
1.在store 中初始化好所有所需属性。
2.当我们需要给state增加一个对象的时候,应该:
- 使用 Vue.set(obj, 'newProp', 123)
- 以新对象替换老对象
state.obj = { ...state.obj, newProp: 123 }
复制代码
3)使用常量代替Mutations的事件类型type
把大量的事件类型规划到一个文件中(一般叫做mutations-types.js),可以让项目中整体的mutations一目了然
//mutations-types.js
export const increment="increment";
// store.js
import Vuex from 'vuex'
import { SOME_MUTATION } from './mutation-types'
const store = new Vuex.Store({
state: { ... },
mutations: {
[SOME_MUTATION] (state) {
// mutate state
}
}
})
复制代码
Actions
对于actions,也是以对象的类型注入到Store中,与mutation类似却又有截然不同之处:
- action不能向mutation一样直接更改state,它提交的是mutation.
- action可以包含异步操作,所以异步的操作都在这个阶段完成.
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment (state) {
state.count++
}
},
actions: {
increment (context) {
context.commit('increment')
}
}
})
复制代码
action函数默认第一个参数是具有与Store对象方法和属性相同的对象context,但是context不等于Store;
我们一般会使用解构的方法,使用里面的
`commit`方法用来提交mutation ,
`state`方法来获取state,或者`getters`,拿到getters,
`dispatch`方法用来在当前action中派发其他action
复制代码
actions: {
increment ({commit}) {
commit('increment')
}
}
复制代码
派发action
通过store.dispatch
派发action,和mutation类似,每一个dispatch函数第一个参数是字符串类型的派发标识,同样支持载荷,即第二个参数是传递给action的数据.
// 以载荷形式分发
store.dispatch('increment', {
amount: 10
})
// 以对象形式分发
store.dispatch({
type: 'increment',
amount: 10
})
复制代码
组件中派发action
同样也是分为两类:
1.使用this.$store.dispatch
import { mapActions } from 'vuex'
export default {
// ...
methods: {
add(){
this.$store.dispatch("add")
}
}
}
复制代码
2.使用mapActions辅助函数
import { mapActions } from 'vuex'
export default {
// ...
methods: {
...mapActions([
//将this.increment 映射为this.$store.dispatch("increment")
'increment',
//将this.increment 映射为载荷的this.$store.dispatch("increment",data)
'incrementBy'
])
//第二种 更改名称
...mapActions({
//将this.add()映射为this.$store.dispatch('increment')
add: 'increment'
})
}
}
复制代码
组合派发action
action多数情况下是包含异步操作的,我们如果想实现一个action派发结束后派发另一个action,就必须要之前前一个dispatch结束的时间. store.dispatch 可以处理被触发的 action 的处理函数返回的 Promise,并且 store.dispatch 仍旧返回 Promise
this.$store.dispatch("add").then(()=>{this.$store.dispatch("reset")})
//也可以在另一个action中这样操作
actions:{
actionA({commit}){
//...
},
async actionB({commit,dispatch}){
awit dispatch("actionA")
commit("add")
}
}
复制代码
Module
Module的出现主要是为了解决由于庞大的项目中所有的状态都集中于一个对象而变得臃肿复杂。 对于每个模块(module)拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割.
const moduleA = {
state: { A:"A" },
mutations: { ... },
actions: { ... },
getters: { ... }
}
const moduleB = {
state: { B:"B" },
mutations: { ... },
actions: { ... }
}
const store = new Vuex.Store({
modules: {
a: moduleA,
b: moduleB
},
state:{
n:1
}
})
store.state.a.A // -> "A"
store.state.b.B // -> "B"
store.state.n//全局state中n的值 1
复制代码
Module中mutation,getter,action
对于模块中的mutation,getter第一个参数则是当前模块的局部状态state,仅仅代表是当前模块下的state .
const moduleA = {
state: { count: 0 },
mutations: {
increment (state) {
// 这里的 `state` 对象是模块的局部状态
state.count++
}
},
getters: {
doubleCount (state) {
return state.count * 2
}
}
}
复制代码
对于当前模块内部的action,context.state
则仅仅代表着当前模块下的state,context.rootState
则代表根节点下的状态state.
const moduleA = {
// ...
actions: {
add({ state, commit, rootState }) {
if ((state.count + rootState.count) % 2 === 1) {
commit('increment')
}
}
}
}
复制代码
对于当前模块内部的getter,根节点状态则作为第三个参数暴露出来
const moduleA = {
// ...
getters: {
get(state, getters, rootState) {
return state.count + rootState.count
}
}
}
复制代码
命名空间namespaced
默认情况下,每个模块内部的action,mutation,getters都算是注册在全局命名空间下的,这样能够使多个模块能够对同一mutation或者action做出相应,但是个人认为这样会显得代码很凌乱,复用性不高。 所以,为了让模块具有更高的封装度和复用性,可以通过给模块添加
namespaced: true
的方式使其成为带命名空间的模块 当模块被注册后,它的所有 getter、action 及 mutation 都会自动根据模块注册的路径调整命名.
const store = new Vuex.Store({
modules: {
account: {
//在当前模块下添加命名空间
namespaced: true,
state: { ... }, // 模块内的状态已经是嵌套的了,使用 `namespaced` 属性不会对其产生影响,所以获取方式仍然是 store.state.account
getters: {
//此时模块中getter会根据注册的路径调整命名 account代表当前所在模块命名空间,isAdim代表当前getter的名称
//组件内部调用方式为:this.$store.getters["account/isAdim"]
isAdmin () { ... }
},
actions: {
//组件内部调用方式为:this.$store.getters["account/login"]
login () { ... } // -> dispatch('account/login')
},
mutations: {
组件内部调用方式为:this.$store.getters["account/login"]
login () { ... } // -> commit('account/login')
},
// 嵌套模块 嵌套的子模块中如果没有命名空间,则继承父级命名空间
modules: {
// 继承父模块的命名空间 account
myPage: {
state: { ... },
getters: {
profile () { ... } // -> getters['account/profile']
}
},
// 进一步嵌套命名空间 如果嵌套的子模块中有命名空间,那么调用内部action,mutation,getter时,会从顶级命名空间一直写到当前命名空间 account/posts
posts: {
namespaced: true,
state: { ... },
getters: {
popular () { ... } // -> getters['account/posts/popular']
}
}
}
}
}
})
复制代码
命名空间的模块中访问全局内容
很多情况下,我们想在一个
namespaced: true
即带有命名空间的模块,派发dispatch-->action
或者使用getter
时,希望使用到全局的state和getter.
action中通过 context 对象的属性传入
rootState
和rootGetter
getter中则将
rootState
和rootGetter
做为第三个和第四个参数传入函数中如果想在带有命名空间的子模块中,派发或提交
action
或mutation
时,直接将**{root:true}作为第三个参数传入dispatch 或 commit** 即可
modules: {
foo: {
namespaced: true,//当前模块命名空间为 foo
getters: {
// 在这个模块的 getter 中,`getters` 被局部化了
// 你可以使用 getter 的第四个参数来调用 `rootGetters`
someGetter (state, getters, rootState, rootGetters) {
//调用当前模块中的getter
getters.someOtherGetter // -> 'foo/someOtherGetter'
//调用全局命名空间下的getter
rootGetters.someOtherGetter // -> 'someOtherGetter'
//调用其他模块下的getter 比如 A模块下的add getter A/add
rootGetters["A/add"] // -> "A/add"
},
someOtherGetter: state => { ... }
},
actions: {
// 在这个模块中, dispatch 和 commit 也被局部化了
// 他们可以接受 `root` 属性以访问根 dispatch 或 commit
someAction ({ dispatch, commit, getters, rootGetters }) {
getters.someGetter // -> 'foo/someGetter'
rootGetters.someGetter // -> 'someGetter'
dispatch('someOtherAction') // -> 'foo/someOtherAction'
dispatch('someOtherAction', null, { root: true }) // -> 'someOtherAction'
//派发 B模块中 add action
dispatch('A/add') // -> 'A/add'
//commit同理
commit('someMutation') // -> 'foo/someMutation'
commit('someMutation', null, { root: true }) // -> 'someMutation'
},
someOtherAction (ctx, payload) { ... }
}
}
}
复制代码
在命名空间的子模块中注册全局action
若需要在带命名空间的模块注册全局 action,你可添加 root: true,并将这个 action 的定义放在函数 handler 中。例如:
{
actions: {
someOtherAction ({dispatch}) {
dispatch('someAction')
}
},
modules: {
foo: {
namespaced: true,//带有命名空间的子模块
actions: {
someAction: {
root: true,//这个标识表示此action注册在全局
handler (namespacedContext, payload) { ... } // -> 'someAction'
}
}
}
}
}
复制代码
带有命名空间的模块中使用辅助函数
当使用 mapState, mapGetters, mapActions 和 mapMutations 这些函数来绑定带命名空间的模块时,写起来可能比较繁琐:
第一种方式
按照命名空间的路径一步一步写到对应
state,action,mutation,getter
.
computed: {
...mapState({
a: state => state.some.nested.module.a,
b: state => state.some.nested.module.b
})
},
methods: {
...mapActions([
'some/nested/module/foo', // -> this['some/nested/module/foo']()
'some/nested/module/bar' // -> this['some/nested/module/bar']()
])
...mapActions({
A:'some/nested/module/foo', //this.A==>this.$store.commit("some/nested/module/foo")
B:'some/nested/module/bar'//this.B==>this.$store.commit("some/nested/module/bar")
})
}
复制代码
第二种方式
很明显第一种方式写起来很繁琐,看起来也影响美观,对于这种情况,第二种方式就是将模块的空间名称路径字符串作为第一个参数传递给上述函数,这样所有绑定都会自动将该模块作为上下文。于是上面的例子可以简化为:
computed: {
...mapState("some.nested.module",{
a: state => state.a,
b: state => state.b
})
},
methods: {
...mapActions("some/nested/module",[
'foo', // -> this['some/nested/module/foo']()
'bar' // -> this['some/nested/module/bar']()
])
...mapActions("some/nested/module",{
A:'foo', //this.A==>this.$store.commit("some/nested/module/foo")
B:'bar'//this.B==>this.$store.commit("some/nested/module/bar")
})
}
复制代码
第三种方式
使用Vuex中的createNamespacedHelpers方法,创建基于某个命名空间辅助函数。它返回一个对象,对象里有新的绑定在给定命名空间值上的组件绑定辅助函数:
import { createNamespacedHelpers } from 'vuex'
const { mapState, mapActions } = createNamespacedHelpers('some/nested/module')
export default {
computed: {
// 在 `some/nested/module` 中查找
...mapState({
a: state => state.a,
b: state => state.b
})
},
methods: {
// 在 `some/nested/module` 中查找
...mapActions([
'foo',
'bar'
])
}
}
复制代码
结语
到此Vuex中的核心概念基本就介绍完了。本文主要是基于官方文档进行了简单的整理,整个过程中也使我自己受益匪浅,写这篇文章也希望可以帮助到更多像我一样想要学习探索Vue的初学者.
.
.
如果转载请标注出自@Lsq