Vuex入门---了解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, dispatchcommmit.
  • 也就是说,你在使用模块内容时,不需要在同一模块内额外添加空间名前缀
  • 更改namespaced属性后不需要修改模块内的代码

在带命名空间的模块内访问全局内容

如果你希望使用全局state和getter, rootStaterootGetters会作为第三和第四参数传入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,mapActionsmapMutations 这些函数来帮顶带命名空间的模块时,写起来可能比较繁琐
例如:

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.myModulestore.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等等...
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值