vue中的vuex状态管理:从入门到精通

Vuex

当我们的应用遇到多个组件共享状态时,单向数据流的简洁性很容易被破坏:

  • 多个视图依赖于同一状态。
  • 来自不同视图的行为需要变更同一状态。

对于问题一,传参的方法对于多层嵌套的组件将会非常繁琐,并且对于兄弟组件间的状态传递无能为力。对于问题二,我们经常会采用父子组件直接引用或者通过事件来变更和同步状态的多份拷贝。以上的这些模式非常脆弱,通常会导致无法维护的代码。

什么情况下我应该使用 Vuex?

Vuex 可以帮助我们管理共享状态,并附带了更多的概念和框架。这需要对短期和长期效益进行权衡。

如果您不打算开发大型单页应用,使用 Vuex 可能是繁琐冗余的。确实是如此——如果您的应用够简单,您最好不要使用 Vuex。一个简单的 store 模式简单状态管理起步使用)就足够您所需了。但是,如果您需要构建一个中大型单页应用,您很可能会考虑如何更好地在组件外部管理状态,Vuex 将会成为自然而然的选择。

Vuex是vue的状态管理工具,为了更方便的实现多个组件共享状态。

(以上内容来自官网)

基本使用

安装

npm install vuex --save

使用

  1. 引入vuex
import Vuex from 'vuex';
  1. 使用vuex
Vue.use(Vuex);
  1. 创建store实例
const store = new Vuex.Store({
  state: {
    count: 0
  }
})
  1. 将store挂载上
new Vue({
  store,
})

State

state故名思意是状态,其实就是vuex存放数据的地方,我们可以将多个组件需要共同的使用的变量放在state中,state是一个对象。

使用state

我们将变量放在state中,那么如何使用呢?

Vuex 通过store 选项,提供了一种机制将状态从跟组件“注入”到每一个子组件中(调用Vue.use(Vuex))。通过在根实例中注册store选项,该store实例会注入到根组件下的所有子组件中,且子组件能通过this.$store访问。像这样:

<div>
  喜欢人数: {{ $store.state.count }}
</div>

由于 Vuex 的状态存储是响应式的,从 store 实例中读取状态最简单的方法就是在计算属性中返回某个状态:

computed: {
    count () {
      return this.$store.state.count
    }
}

mapState 辅助函数

当一个组件需要获取多个状态时,将这些状态都声明为计算属性会有些重复和冗余。为了解决这个问题,我们可以使用mapState辅助函数帮助我们生成计算属性:

import { mapState } from 'vuex';

computed: {
  ...mapState(['count']),
}

此时就有一个计算属性count,我们就可以直接使用它了,也可以为它重新起一个别名

computed: {
  ...mapState({
    storeCount: state => state.count,
  })
}

如果仅仅只是返回一个state中的一个数据,我们也可以简写:

computed: {
  ...mapState({
     storeCount: 'count', // 等同于 state => state.count
  })
}

Getter

有时候我们可以能需要从state中派生一下状态,比如说,我们需要对count进行双倍处理,我们就可以使用getter,我们可以把getter认为是store中的计算属性。getter的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。

Getter 接收state作为其第一个参数,Getter 也可以接受其他 getter 作为第二个参数

getters: {
  countDouble (state) {
    return state.count * 2;
  }
}

通过属性访问

Getter会暴露为store.getters对象:this.$store.getters.countDouble

<div>
  喜欢人数的两倍: {{ $store.getters.countDouble }}
</div>

通过方法访问

也可以让getter返回一个函数,来实现给getter传参

getters: {
  countAdd: state => num => state.count + num;
  /* 
  	countAdd:function(state){
  		return function(num){
  			return state.count + num
  		}
  	}
  */
}
this.$store.countAdd(3);

mapGetters 辅助函数

mapState相似,我们也可以通过mapGettersstore种的getters映射到计算属性种

import { mapsGetters } from 'vuex';

computed: {
   ...mapGetters([
   	  'countDouble',
   	  'countAdd',
   ])
}

如果你想将一个 getter 属性另取一个名字,使用对象形式:

mapGetters({
  storeCountDouble: 'countDouble'
})

Mutation

在严格模式下,更改 Vuexstore 中的状态的唯一方法是提交 mutation

const store = new Vuex.Store({
  state: {
    count: 1
  },
  mutations: {
    increment (state) {
      // 变更状态
      state.count++
    }
  }
})

在组件中提交 Mutation

但是我们不能直接调用mutation,当我们想要使用时,我们可以条用store.commit方法来触发一个mutation handler。像这样:

this.$store.commit('increment');

mapMutations 辅助函数

当然我们也可以使用辅助函数,例如:

import { mapMutations } from 'vuex'

methods: {
    ...mapMutations([
    	'increment', 
    ]),
    ...mapMutations({ //同样的起一个别名
    	add: 'increment' 
    })
}

辅助函数会将this.increment()this.add()映射为this.$store.commit('increment')

提交载荷(Payload)

你可以向store.commit传入额外的参数,官方的学术名称叫载荷(payload):

mutations: {
  increment (state, num) {
    state.count += num
  }
}
this.$store.commit('increment', 10)

在大多数情况下,载荷应该是一个对象,这样可以包含多个字段并且记录的mutation会更易读:

mutations: {
  increment (state, payload) {
    state.count += payload.num
  }
}
this.$store.commit('increment', {
  num: 10
})

对象风格的提交方式

提交 mutation 的另一种方式是直接使用包含 type 属性的对象:

this.$store.commit({
  type: 'increment',
  num: 10
})

当使用对象风格的提交方式,整个对象都作为载荷传给 mutation 函数,因此 handler 保持不变:

mutations: {
  increment (state, payload) {
    state.count += payload.num
  }
}

使用常量替代 Mutation 事件类型

把这些常量放在单独的文件中可以让你的代码合作者对整个 app 包含的 mutation 一目了然:

// mutation-types.js
export const COUNT_INCREMENT = 'COUNT_INCREMENT'
// store.js
import Vuex from 'vuex'
import { COUNT_INCREMENT } from './mutation-types'

const store = new Vuex.Store({
  state: { count:0 },
  mutations: {
    [COUNT_INCREMENT] (state) {
      	state.count++
    }
  }
})

用不用常量取决于自己,在需要多人协作的大型项目中,这会很有帮助。我在开发大型项目的时候,通常都会用。

Mutation 需遵守 Vue 的响应规则

下面是官网的解释:

既然 Vuexstore 中的状态是响应式的,那么当我们变更状态时,监视状态的 Vue 组件也会自动更新。这也意味着 Vuex 中的 mutation 也需要与使用 Vue 一样遵守一些注意事项:

  1. 最好提前在你的 store 中初始化好所有所需属性。
  2. 当需要在对象上添加新属性时,你应该
  • 使用 Vue.set(obj, 'newProp', 123), 或者

  • 以新对象替换老对象。例如,利用对象展开运算符,我们可以这样写:

    state.obj = { ...state.obj, newProp: 123 }
    

其实原因很简单,当我们使用对象中未有的属性时,vue不会监听,这和vue的知识有关,解决的方法也就是以上两种。

表单处理

Vuexstate 上使用 v-model时,由于会直接更改state的值,所以Vue会抛出错误。因为vuex是不允许直接修改state的,如果想要使用双向数据的功能,就需要自己模拟一个 v-model: :value="msg"@input="updateMsg"

双向绑定的计算属性

上面的做法,比v-model本身繁琐很多,所以我们还可以使用计算属性的setter来实现双向绑定:

<input v-model="mycount">
computed: {
  mycount: {
    get () {
      return this.$store.state.mycount;
    },
    set (value) {
      this.$store.commit(COUNT_INCREMENT, { num: value });
    }
  }
}

Mutation 必须是同步函数

要记住 mutation 必须是同步函数

mutations: {
    increment (state) {
      setTimeout(() => {
     	state.count ++;
      }, 1000)
    }
}

假如,我们正在 debug 一个 app 并且观察 devtool 中的 mutation 日志,执行上端代码,我们会发现更改state的操作是在回调函数中执行的,当 mutation 触发的时候,回调函数还没有被调用,devtools 不知道什么时候回调函数实际上被调用——实质上任何在回调函数中进行的状态的改变都是不可追踪的

严格模式

开启严格模式,仅需在创建 store 的时候传入 strict: true

const store = new Vuex.Store({
  // ...
  strict: true
})

在严格模式下,无论何时发生了状态变更且不是由 mutation 函数引起的,将会抛出错误。这能保证所有的状态变更都能被调试工具跟踪到。

开发环境与发布环境

不要在发布环境下启用严格模式!严格模式会深度监测状态树来检测不合规的状态变更,要确保在发布环境下关闭严格模式,以避免性能损失。我们可以通过 webpack 配合使用:

const store = new Vuex.Store({
  // ...
  strict: process.env.NODE_ENV !== 'production'
})

Action

Action 类似于 mutation,不同在于:

  • Action 提交的是 mutation,而不是直接变更状态。
  • Action 可以包含任意异步操作

Action 函数接收一个与 store 实例具有相同方法和属性的 context 对象,因此你可以调用 context.commit 提交一个 mutation,或者通过 context.statecontext.getters 来获取 state 和 getters:

const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment (state) {
      state.count++
    }
  },
  actions: {
    increment (context) {
      console.log(context.state.count) //0
      context.commit('increment')
    }
  }
})

分发Action

同样的我们在组件重调用action,需要使用 $store.dispatch

this.$store.dispatch('increment')

虽然和mutation差不多,但是在action中,可以执行异步操作,但是mutation中不行!!!

actions: {
  incrementAsync ({ commit }) {
    setTimeout(() => {
      commit('increment')
    }, 1000)
  }
}

组合 Action

Action 通常是异步的,那么如何知道 action 什么时候结束呢?我们可以使用ES6中的Promise,在项目中,通常也是会配合Promise使用

actions: {
  incrementAsync ({ commit }) {//直接使用结构赋值,拿到context中的commit方法
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        commit('increment')
        resolve()
      }, 1000)
    })
  }
}
this.$store.dispatch('incrementAsync').then(() => {
  // ...
    console.log("提交完成!")
})

mapActions 辅助函数

你在组件中使用 this.$store.dispatch('xxx') 分发 action,或者使用 mapActions 辅助函数将组件的 methods 映射为 store.dispatch 调用(需要先在根节点注入 store):

import { mapActions } from 'vuex'

export default {
  // ...
  methods: {
    ...mapActions([
      'increment',
    ]),
    ...mapActions({
      add: 'increment'
    })
  }
}

辅助函数会将 this.increment() 映射为 this.$store.dispatch('increment'),将 this.add() 映射为 this.$store.dispatch('increment')

Module

由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 statemutationactiongetter。像这样:

//count.js
export const count = {
    state: {
        count:0
      },
      getters:{
        countDouble(state){
          return state.count * 2
        }
      },
      mutations: {
        increment(state,payload){
            state.count+=payload.num
        }
      }
}
import {count} from './modules/count'
//...
const store = new Vuex.Store({
  modules: {
    count
  }
})

默认情况下,模块内部的 action、mutation 和 getter 是注册在全局命名空间的——这样使得多个模块能够对同一 mutation 或 action 作出响应。但对于模块内部的 state 比较特殊,会将 module 模块中的 state 放在一个对象中,对象名就是模块的名字,在开发者工具中我们可以看到如下:
在这里插入图片描述

  • 获取 state:state,所以也无法直接通过mapState获取state,需要加上模块名,想这样:
this.$store.state.count.count
//mapState方式
computed:{
    ...mapState({
        count:state=>state.count.count
    })
},
  • 获取 getter
this.$store.getters.countDouble
//mapGetter方式
computed:{
    ...mapGetters({
    	countDouble:"countDouble"
	})
}
  • 提交 mutation
this.$store.commit('increment',{num:2});
//mapMutation方式
methods:{
    ...mapMutations(['increment'])
}
  • 分发 action
this.$store.dispatch('xxx');
//mapActions方式
methods:{
    ...mapActions方式(['xxx'])
}

命名空间

我们上面也说过了,默认情况下,模块内部的 action、mutation 和 getter 是注册在全局命名空间的,如果你想要action、mutation 和 getter 同样是局部的话,可以通过添加 namespaced: true 的方式使其成为带命名空间的模块。

//count.js
export const count = {
    //...
    namespaced: true,
}
  • 获取 state:和没用命名空间相同
this.$store.state.count.count
//mapState方式
computed:{
    ...mapState({
        count:state=>state.count.count
    })
}
  • 获取 getterthis.$store.getters['count/doubleCount']
this.$store.getters['count/countDouble']
//mapGetter方式
computed:{
    ...mapGetters({
         countDouble:"count/countDouble"
    })
}
  • 提交 mutationthis.$store.commit('moduleName/xxx')
this.$store.commit("count/increment",{num:2})
//mapMutation方式
methods:{
    ...mapMutations({
    	add:'count/increment'
	})
}

在使用的时候,最好像上面例子中一样为mutaions起一个别名,否则在使用的时候就是这样调用的了this[count/increment]

  • 分发 actionthis.$store.dispatch('moduleName/xxx')
this.$store.dispatch("count/xxx")
//mapActions方式
methods:{
    ...mapActions方式({
    	actionCount:'count/xxx'
    })
}

以上就是使用命名空间的时候使用方法,虽然我在项目中没有使用过,但感觉应该有用。

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

如果我们想要在命名空间的模块内访问全局内容呢?我们看一下官网上的用法,

如果你希望使用全局 state 和 getter,rootStaterootGetters 会作为第三和第四参数传入 getter,也会通过 context 对象的属性传入 action。

modules: {
  foo: {
    namespaced: true,
    getters: {
      // 在这个模块的 getter 中,`getters` 被局部化了
      // 你可以使用 getter 的第四个参数来调用 `rootGetters`
      someGetter (state, getters, rootState, rootGetters) {
       	//...
      },
    },
  }
}

再具体用法见官网吧:https://vuex.vuejs.org/zh/guide/modules.html

  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值