状态管理模式,采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化
vue提供了直接下载和CDN引用两种方式:
//CDN:
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://unpkg.com/vuex"></script>
!!应该先引用vue,再引用vuex
//NPM:
npm install vuex --save
//Yarn
yarn add vuex
//在一个模块化的打包系统中,必须显示地通过Vue.use()来安装Vuex:
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
Vuex依赖Promise,如果项目需要支持没有实现Promise的IE,需要使用一个polyfill的库,例如es6-promise,同样可以通过直接下载或CDN引用的方式:
//CDN引入之后window.Promise自动可用
<script src="https://cdn.jsdelivr.net/npm/es6-promise@4/dist/es6-promise.auto.js"></script>
//使用npm/yarn等方式下载
npm install es6-promise --save
yarn add es6-promise
//在使用vuex之前添加
import 'es6-promise/auto'
每一个Vuex应用的核心就是store(仓库),store基本上就是一个容器,包含着应用中大部分的状态,Vuex和单纯的全局对象有两点不同:
1.Vuex的状态存储是响应式的,当Vue组件从store中读取状态的时候,若store中的状态发生变化,那么相应的组件也会相应地得到高效更新
2.不能直接改变store的状态,改变store的状态的唯一途径就是显式地提交(commit)mutation,使得方便跟踪每一个状态的变化
安装Vuex之后,创建一个store,创建过程直截了当 – 仅需要提供一个初始state对象和一些mutation:
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const store = new Vuex.Store({
state:{
count:0
},
mutation:{
increment(state){
state.count++
}
}
})
//现在你可以通过state.count来获取状态对象,也可以通过store.commit方法触发状态变更:
store.commit('increment')
console.log(store.state.count) //1
Vuex提供了一个从根组件向所有子组件,以store选项的方式注入该store的机制:
new Vue({
el:'#app',
store:store
})
//现在可以从组件的方法提交一个变更:
method:{
increment(){
this.$store.commit('increment')
console.log(this.$store.state.count)
}
}
在vue组件中获得Vuex状态:
//创建一个Count组件
const Counter = {
template:`<div>{{ count }}</div>`,
computed:{
count(){
return store.state.count
}
}
}
mapState辅助函数,当一个组件需要获取多个状态的时候,将这些状态都声明为计算属性会有些重复和冗余,为了解决这个问题,可以使用mapState辅助函数帮助我们生成计算属性:
import { mapState } from 'vuex'
export default{
computed:mapState({
count:state => state.count,
// 传字符串参数‘count’等同于'state => state.count'
countAlias:'count',
})
}
当映射的计算属性的名称与state的子节点名称相同时,也可以给mapState传一个字符串数组
computed: mapState(['count'])
mapState返回的是一个对象,但是有时候我们的计算属性不只用来存储状态,还会定义一些计算属性,如何写在一起呢?可以利用对象展开运算符:
computed:{
localComputed(){ /* ... */ },
...mapState({
//...
})
}
Getters
有时候我们需要从store中的state中派生出一些状态,例如对列表进行过滤并计数:
computed:{
doneTodosCount(){
return this.$store.state.todos.filter(todo => todo.done).length
}
}
如果有多个组件需要用到这个属性,我们一般会复制这个函数,或者抽取到一个共享函数然后在多处导入它,无论哪种方式都不是很理想,vuex允许我们在store中定义getter,可以认为是store的计算属性,就算计算属性一样,getter的返回值会根据它的依赖被缓存起来,且只有当它的依赖发生了改变才会被重新计算:
const store = new Vuex.Store({
state:{
todos:[
{ id:1,text:'...',done:true },
{ id:2,text:'...',done:false }
]
},
getters:{
//Getter接受state作为其第一个参数
doneTodos:state => {
return state.todos.filter(todo => todo.done)
},
//也可以接受其他getter作为第二个参数
doneTodosCount:(state,getters)=>{
return getters.doneTodos.length
}
}
})
//通过属性访问
store.getters.doneTodos // => [{id:1,text:'...',done:true}]
store.getters.doneTodosCount // 1
//也可以通过方法访问,通过让getter返回一个函数,来实现给getter传参,在对store里的数据进行查询时非常有用
getters:{
getTodoById: state => id => {
return state.todos.find(todo => todo.id === id)
}
}
store.getters.getTodoById(2) //{id:2,text:'...',todo:false}
//通过mapGetters辅助函数访问
import { mapGetters } from 'vuex'
export default{
computed:{
...mapGetters([
'doneTodosCount',
'anotherGetter',
'doneCount':'doneTodosCount'
])
}
}
Mutation
更改Vuex的store中的状态的唯一方法是提交mutation,Vuex中的mutation非常类似于事件:
const store = new Vuex.Store({
state: {
count: 1
},
mutations: {
increment (state) {
// 变更状态
state.count++
}
}
})
store.commit('increment')
//也可以向store.commit传入额外的参数,即mutation的载荷:
mutations:{
increment(state,n){
state.count += n
}
}
store.commit('increment',10)
//大多数情况下,载荷应该是一个对象,这样可以包含多个字段并且记录的mutation会更易读:
mutations:{
increment(state,payload){
state.count += payload.amount
}
}
store.commit('increment',{
amount:10
})
//也可以采用对象风格的提交方式
store.commit({
type:'increment',
amount:10
})
当使用对象风格的提交方式,整个对象都作为载荷传给 mutation 函数,因此 handler 保持不变
Mutation需要遵守Vue的响应规则
1.最好提前在你的store中初始化所有所需属性
2.当需要在对象上添加新属性时,应该:
//使用Vue.set
Vue.set(obj,'newProp',123)
//或者以新对象替换老对象
state.obj = {...state.obj, newProp:123}
使用常量替代Mutation事件类型
使用常量替代 mutation 事件类型在各种 Flux 实现中是很常见的模式。这样可以使 linter 之类的工具发挥作用,同时把这些常量放在单独的文件中可以让你的代码合作者对整个 app 包含的 mutation 一目了然:
//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:{...},
mutation:{
[SOME_MUTATION](state){
//mutate state
}
}
})
mutation只能是同步函数
在组件中提交Mutation,可以在组件中使用this.$store,commit(‘xxx’)提交mutation,或者使用mapMutations辅助函数将组件中的methods映射为store.commit调用
import { mapMutations } from 'vuex'
export default{
methods:{
...mapMutations([
'increment',
//使用this.increment() 会映射为 this.$store.commit('increment')
'incrementBy',
//this.incrementBy(amount) 会映射为 this.$store.commit('crementBy',amount)
]),
...mapMutations({
add:'increment'
//this.add() 会映射为this.$store.commit('increment')
})
}
}
Action,类似于mutation,不同在于:
1.Action提交的是mutation,而不是直接变更状态
2.Action可以包含任意异步操作
const store = new Vuex.Store({
state:{
count:0
},
mutations:{
increment(state){
state.count++
}
},
actions:{
increment(context){
context.commit('increment')
}
}
})
Action 函数接受一个与 store 实例具有相同方法和属性的 context 对象,因此你可以调用 context.commit 提交一个 mutation,或者通过 context.state 和 context.getters 来获取 state 和 getters。也可以利用参数解构的方式:
actions:{
increment({coommit}){
commit('increment')
}
}
//Action通过store.dispatch方法触发
store.dispatch('increment')
你可能会问,为什么要这么麻烦呢,不直接commit一个mutation就好了,不是的,我们的mutation只能进行同步操作,但是有时候我们需要执行异步操作,Action就不受约束:
actions:{
incrementAsync({commit}){
setTimeout(()=>{
commit('increment')
},1000)
}
}
//Actions支持同样的载荷方式和对象方式进行分发
//以载荷形式分发
store.dispatch('incrementAsync',{
amount:10
})
//以对象形式分发
来看一个更加实际的购物车示例,涉及到调用异步API和分发多重mutation:
actions:{
checkout({commit,state},products){
//把当前购物车的物品备份起来
const savedCartItems = [...state.cart.added]
//发出结账请求,清空购物车
commit(types.CHECKOUT_REQUEST)
shop.buyProducts(
products,
//成功操作
() => commit(types.CHECKOUT_SUCCESS),
//失败操作
() => commit(types.CHECKOUT_FAILURE,savedCartItems)
)
}
}
在组件中分发Action,在组件中使用this.$store.dispatch(‘xxx’)分发action,或者使用mapActions辅助函数将组件的methods映射为store.dispatch调用:
import { mapActions } from 'vuex'
export default{
methods:{
...mapActions([
'increment',
//this.increment 会映射为 this.$store.dispatch('increment')
'incrementBy',
//this.incrementBy(amount) 会映射为 this.$store.dispatch('incrementBy',amount)
]),
...mapActions({
add:'increment'
//this.add() 会映射为this.$store.dispatch('crement')
})
}
}
组合Action
Action通常是异步的,那么如何知道action什么时候结束呢,如何结合多个action,处理更加复杂的异步流程?
首先需要明白store.dispatch可以处理被触发的action的处理函数返回的Promise,并且store.dispatch仍旧返回Promise
actions:{
actionA({commit}){
return new Promise((resolve,reject)=>{
setTimeout(()=>{
commit('someMutation')
resolve()
},1000)
})
}
}
store.dispatch('actionA').then(()=>{
//...
})
在另外一个action中也可以:
actions: {
// ...
actionB ({ dispatch, commit }) {
return dispatch('actionA').then(() => {
commit('someOtherMutation')
})
}
}
//如果我们利用 async / await (opens new window),我们可以如下组合 action:
actions: {
async actionA ({ commit }) {
commit('gotData', await getData())
},
async actionB ({ dispatch, commit }) {
await dispatch('actionA') // 等待 actionA 完成
commit('gotOtherData', await getOtherData())
}
}