小陈同学带你入门Vuex
1.Vuex是什么
Vuex是一个专门为Vue.js应用程序开发的 “状态管理模式”
那么状态是什么呢:状态就是数据(记住这句话)
状态管理就是集中式存储管理应用的所有组件的状态(数据)
- 但是并不是所有的场景都适合用Vuex,只有在必要的时候才使用Vuex, 例如在一些复杂的数据通信的应用中就适合使用Vuex
- **对于那些简单的项目可以使用vue.js提供的事件总线 **
- 在使用了Vuex之后,就会附加更多的框架中的概念进来,增加了项目的复杂度
2.Vuex的基本使用
1.安装vuex
yarn add vuex
2.引入vuex,由于vuex是vue的拓展工具,所以必须先引入vue
import Vue from 'vue'
import Vuex from 'vuex'
// 将vuex注册为全局组件
Vue.use(Vuex)
3.使用, 先创建一个store(仓库),每个vuex应用的核心就是store(仓库)
store基本上就是一个容器,它包含着你应用中大部分的状态(state)
// 创建一个vuex的store对象
// 创建过程直截了当 -——> 仅需要提供一个初始 state(状态) 对象和一些 mutation:
const store = new Vuex.Store({
state: {
str: '嘿嘿',
count: 0
},
// mutation : 变化
// 修改vuex中的数据,必须通过提交mutation进行修改
mutations: {
// 配置的matation处理函数
increment (state) {
state.count++
}
}
})
const vm = new Vuex({
el:'#app',
// 关联vm 和 store
store
})
4.访问store中的state
// 直接访问的方法:
console.log(store.state.count)
// 由于建立了关联,可以在组件中通过 vue实例.$store.state的方法来访问
console.log(vm.store.state.count)
5.vue中修改Vuex的数据
- Vuex的状态存储是响应式的.当Vue组件从store中读取状态的时候,若该状态发生变化,那么相应依赖该状态的组件也会相应的得到高效更新
- 你不能直接改变store中的状态. 改变store中的状态唯一途径就是显式的提交 (commit)mutation.这样使得我们可以方便的跟踪每一个状态的变化,从而让我们能够实现一些工具帮助我们更好的了解我们的应用
// 不要在mutation处理函数之外,直接去修改vuex中的数据,例如:
this.$store.state.str = '哈哈'// 这样会直接影响其他依赖这个数据的组件
以上操作,的确可以修改vuex的state , 而且不会报错. 如果你想在写代码的时侯不小心直接修改了state,抛出错误提示的话,就需要开启严格模式,只需要在创建store的时候传入strict:true
const sttore = new Vuex.Store({
state: {...},
strict: true
})
开启严格模式后,将会深度监听状态树,来检测不合规则的状态变更,这反射出这个模式将会影响性能
所以,不要在发布环境下启用严格模式(也就是不要在项目上线还开启该模式),避免一些不必要的性能损耗
如果需要修改数据, 需要提交mutation, 将来就会执行配置好的mutation处理函数 ,(这个方法在后面会详细讲解), 例如:
// 语法: this.$store.commit('mutation函数名')
this.$store.commit('increment')
// 在提交mutation的同时,支持传参,并且只支持传一个参数(如果需要传递多个参数,可以传入一个对象)
// 语法: this.$store.commit('increment', 参数)
this.$store.commit('increment',{
str: '嗯嗯',
count: 3
})
3.vuex 的五大核心概念
1.state
特性:
- Vuex是一个仓库,仓库里面放了很多对象. 其中state就是数据源存放地,对应于与一般Vue对象里的data
- state里面存放的数据是响应式的,Vue组件从store中读取数据,若是store中的数据发生改变,依赖这个数据的组件也会发生更新
- 它通过mapState辅助函数把全局的state映射到当前组件的computed计算属性中
单一状态树
Vuex使用单一状态树模式,用一个对象就包含了全部的应用层级状态,也就是这个对象提供唯一的公共数据源,这也意味着,每个应用将仅仅包含一个store实例. 所有共享的数据都要统一放到Store的state中进行存储,这让我们能够直接定位任一特定的状态片段
在vue组件中访问state中数据
1.直接访问:
this.$store.state.全局数据名
2 使用mapState辅助函数
作用:可以映射一个state属性
// 先导入辅助函数
import { mapState } from 'vuex'
// 将全局的state,映射为当前组件的计算属性
export default {
computed: {
//使用展开运算符'...', 对mapState函数返回的对象进行展开
// 将全局数据,映射为当前组件的计算属性
...mapState(['count'])
}
}
2.Getter
用于对store中的数据进行加工处理,形成新的数据
有时候,我们需要从store中的state中派生出一些状态,例如对列表进行过滤并计数
如果有多个组件需要用到此属性,我们要么复制这个函数,或者抽取到一个共享函数然后在多处导入它
无论哪种方式都不是很理想
- Vuex允许我们在Store中定义Getter(可以认为是store的计算属性),Getter可以对store中已有的数据加工处理之后形成新的数据,类似于Vue的计算属性
- Getter的返回值会根据它的依赖被缓存起来,依赖值发生变化的时候,Getter的数据也会跟着变化
特性:
Getter 可以对State进行计算操作,他就是Store的计算属性,虽然在组件内也可以做计算属性,但是getter可以在多组件之间复用,如果一个状态只在一个组件内使用,是可以不用getter
getter接受state作为其第一个参数:
const store = new Vuex.Store({
state: {
str: 'vuex'
},
getters: {
showStr: state => {
return 'hello' + state.str + ''
}
}
})
使用:
1.直接使用
this.$store.getters.showStr
2 使用mapGetters辅助函数
作用: 仅仅是将store中的getter映射到当前组件的计算属性中
// 先在当前组件导入辅助函数
import { mapGetters } from 'vuex'
export default {
computed:{
...mapGetters(['showStr'])
}
}
3.mutation
特性
初学者可以把它理解为store中的methods,用于变更Store中的数据
mutations对象中,保存着更改数据的回调函数,该函数名官方规定叫做type,第一个参数是state,第二个参数是payload(提交载荷),也就是自定义的参数
- mutations提供的都是方法,这些方法是可以修改state中的数据的
- 所有的方法第一个参数都是state
例子:
mutations: {
// state表示state对象
add(state) {
state.count++
}
}
//++++++++++++++++++++++++++++++
//提交mutations
// 想要修改state,必须触发mutations提供的方法
// 相当于调用了mutations里的add函数,执行add函数里的方法
this.$store.commit('add')
// ++++++++++++++++++++++++++++++++++++
// 在组件中使用mutations
improt { mapMutations } from 'vuex'
methods:{
...mapMutations(['add'])
}
提交载荷
提交载荷就是额外的自定义参数(payload)
- 提交载荷只能传递一个参数,所以,如果有多个参数传入,传入一个对象即可
mmutations: {
add(state,num) {
state.count += num
}
}
this.$store.commit('add',{num:5 ,msg:'嘿嘿' })
注意事项:
- 最好提前在Store中初始化好所有的所需属性
- 只能通过mutation变更Store中的数据,不可直接操作Store中的数据,这样虽然操作起来有点繁琐,但是可以集中监控所有数据的编变化
- mutation 必须是同步函数
4. Action
特性
action类似于mutation
但,不同的是:
- action提交的是mutation,而不是直接变更状态
- action中可以包含异步操作,mutation中绝不允许出现异步
- action中的回调函数的第一个参数是context,是一个与store实例具有相同属性和方法的对象
例子:
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
add() {
state.count++
}
},
actions: {
add (context) {
// 在这里提交mutations--> add
context.commit('add')
}
}
})
由于action函数,接受一个与store实例具有相同方法和属性的context对象
所以你可以调用context.commit提交一个mutations,或者通过context.state和context.getters来获取state和getters
一般在实践中,我们会常用到es6的参数解构来简化代码
actions: {
// 结构参数会将commit从context中解构出来单独用
add ({commit}) {
commit('add')
}
}
触发Action
按照官网的叫法触发Action被叫做分发
触发action方法是:
//语法: 所传的两个参数一个是要触发action的类型,一个是所携带的数据(payload)
this.$store.dispatch(actionType,payload)
// 以载荷方式分发
this.$store.dispatch('add',{
amount: 10
})
// 以对象的形式分发
this.$store.dospatch({
type: 'add',
amount: 10
})
还有一种方法是使用mapActions辅助函数将组件的methods映射为this.$store.dispatch调用:
import { mapActions } form 'vuex'
export default {
// ...
methods: {
...mapActions([
'add',
])
}
}
另外,this.$store.dispatch
可以处理被处罚的action的处理函数返回的Promise
并且,this.$store.dispatch
任然返回Promise
组合action
action通常是异步的,那该如何知道action什么时候结束
又如何才能组合多个action,以处理更加复杂的异步流程呢
首先store.dispatch可以处理,被触发的action的处理函数返回的Promise,并且store.dispatch任然返回Promise
actions: {
add ({commit}) {
return new Promise((resolve,reject) => {
setTimeout(() => {
commit('someMutation')
resolve()
},1000)
})
}
}
现在可以这样:
store.dispatch('add'),then(() => {
//...你的代码
})
在另外一个actions中也可以这样:
actions: {
addA({ dispatch,commit }){
return dispatch('add').then(() => {
commit('someOtherMutation')
})
}
}
由于返回的是Promise对象,那么我们就可以使用async和await组合:
// 假设getData()和getOtherData()返回的是promise
actions: {
async add({commit}) {
commit('gotData',await getData())
},
async addA({ dispatch, commit}) {
await dispatch('add') // 等待add完成
commit('gotOtherData', await getOtherData())
}
}
注意:
一个store.dispatch在不同的模块中可以触发多个action函数.在这种情况下,只有当所有触发函数完成后,返回的Promise才会执行
5.Modules
由于使用单一状态树,应用的所有状态会集中到一个比较大的对象
当应用变得非常复杂时,store对象就有可能变得相当臃肿
为了解决以上问题,Vuex允许store分割成模块(module),
每个模块拥有自己的state,mutation,action,getter,甚至时嵌套子模块,从上至下进行同样方式的分割
const moduleA = {
state: () => ({...}),
mutations: {...},
actions: {...},
getters: {...}
}
cosnt 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: {
//这里的state对象是模块的局部state数据
add(state) {
state.count++
}
},
getters: {
add2 (state) {
return state.count * 2
}
}
}
同样,对于模块内部的action, 局部状态通过context.state暴露出来,根节点状态则为context.rootState
const moduleA = {
//....
actions: {
addRootSum ({state, commit, rootState}) {
//....
}
}
}
对于模块内部的getter,根节点状态会作为第三个参数暴露出来
const moduleA = {
//...
getters: {
sumRoot (state, getters, rootState) {
return state.count + rootState.count
}
}
}
命名空间
默认情况下,模块内部的actions,mutations和getters是注册在全局命名空间的
这样使得多个模块能够对同一mutation或者action做出响应
如果希望模块具有更高的封装度和复用性,你可以通过添加namespaced: true
的方式使其成为带命名空间的模块
当模块被注册后,它的所有getter,action及mutation都会自动根据模块注册的路径调整命名
const store = new Vuex.Store({
modules: {
account: {
namespaced: true,
// 模块内的状态已经是嵌套的了,使用 namespaced 属性不会对其产生影响
state: () => ({...}),
getters: {
isAdmin () {...}
},
actions: {
login () {...}
},
mutations: {
login() {...}
},
// 模块嵌套
modules: {
// 继承父模块的命名空间
myPage: {
state: () => ({ ... }),
getters: {
profile () {...}
}
},
// 进一步嵌套命名空间
posts: {
namespaced: true,
state: () => ({ ... }),
getters: {
popular() {...}
}
}
}
}
}
})
- 启动了命名空间的getter和action会收到局部化的
getter, dispatch
和commmit.
- 也就是说,你在使用模块内容时,不需要在同一模块内额外添加空间名前缀
- 更改
namespaced
属性后不需要修改模块内的代码
在带命名空间的模块内访问全局内容
如果你希望使用全局state和getter, rootState
和rootGetters
会作为第三和第四参数传入getter
,也会通过context
对象的属性传入action
若是需要在全局命名空间内分发action或提交mutation,将{ root: true }作为第三参数传给dispatch
或者commit
即可
modules: {
foo: {
namespaced: true,
getters: {
//在这个模块的getter中, getters 被局部化了
// 你可以使用getter的第四个参数来调用 rootGetters
someGetter (state, getters, rootState, rootGetters) {
getters.someOtherGetter // -> 'foo/someOtherGetter'
rootGetters.someOtherGetter // -> 'someOtherGetter'
},
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'
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,
handler (namespacedContext, payload) {...}
}
}
}
}
}
带命名空间的绑定函数
当使用mapState
,mapGetters
,mapActions
和mapMutations
这些函数来帮顶带命名空间的模块时,写起来可能比较繁琐
例如:
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']()
])
}
对于这种情况,可以将模块的空间名称字符串作为第一个参数传递给上述函数
这样所有绑定都会自动将该模块作为上下文
computed: {
...mapState('some/nested.module',{
a: state => state.a,
b: state => state.b
})
},
methods: {
...ampActions('some/nested/module',[
'foo',
'bar'
])
}
而且,可以通过使用createNamespaceHelpers
创建基于某个命名空间辅助函数
它返回一个对象,对象里有新的绑定在给命名空间值上的组件绑定辅助函数
import { createNamespaceHelpers } from 'vuex'
// 从它返回的对象里解构出辅助函数
const { mapState, mapActions } = createNamespacedHelpers('some/nested/module')
export default{
computed: {
...mapState({
a:state => state.a
b: state = > state.b
})
},
methods: {
// 在some/nested/module 中查找
...mapActions([
'foo',
'bar'
])
}
}
模块动态注册
在创建store之后,可以使用store.refisterModule
方法注册模块
//注册模块 myModule
store.registerModule('myModel',{
// ...
})
// 注册嵌套模块 nested/myModule
store.registerModule(['nestedd', 'myModule'],{
// ...
})
然后你就可以通过store.state.myModule
和 store.state.nested.myModule
访问模块的状态
模块动态注册功能使得其他Vue插件可以通过在store中附加新模块的方式来使用Vuex管理状态
例如: vuex-router-aync
插件就是通过动态注册模块将vue-router和vuex结合在一起使用,实现应用的路由状态管理
你也可以通过使用 store.unregisterModule(moduleName)
来动态卸载模块,
但是不能通过这个方法来卸载静态模块(也就是创建store时声明的模块)
你还可以通过store.hasModule(moduleName)
方法检查该模块是否被注册到store
保留state
在注册一个新的module的时候,你可能很想保留过去的state,例如从服务器端渲染的应用保留state
这时候你可以通过preserveState
将其归档:
store.registerModule('a', module, { preserveState: true })
当你设置preserveState: true
时,该模块会被注册. action,mutation 和getter会被添加到store中 , 但是state不会
模块重用
有时候可能需要创建一个模块的多个实例,例如:
- 创建多个store,他们公用一个模块
- 在一个store中多次注册同一个模块
如果我们使用一个纯对象来声明模块的状态,那么这个状态对象会通过引用被共享,导致状态被修改时store或模块间数据相互污染的问题
实际上这和vue组件内的data是同样的问题
因此解决方法也是相同的: 使用一个函数来声明该模块
const myModule = {
state: () => ({
foo: 'bar'
}),
//mutation,action和getter等等...
}