本文基于上一篇文章:Vue系列入门教程(14)-vuex入门案例
VueX中的核心成员列表如下:
state
: 存放状态,类似于vue组件中的data
mutations
:操作state
成员,必须是同步方法,类似于vue组件中的methods
getters
: 加工state
成员给外界,类似于vue组件中的computed
actions
: 异步操作modules
: 模块化状态管理
state的使用在上篇文章演示过了,存储在 Vuex 中的数据和 Vue 实例中的 data
遵循相同的规则,可以通过this.$store
在所有组件中获取。
有时候我们需要从 store 中的 state 中派生出一些状态,例如对列表进行过滤并计数,不适应vuex情况下,我们可能会用计算属性:
computed: { doneTodosCount () { return this.$store.state.todos.filter(todo => todo.done).length } }
如果有多个组件需要用到此属性,我们要么复制这个函数,或者抽取到一个共享函数然后在多处导入它——无论哪种方式都不是很理想。Vuex 允许我们在 store 中定义“getter”(可以认为是 store 的计算属性)。就像计算属性一样,getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。
1、将上篇文章中的
vuex-app
项目中store
下的index.js
修改如下:
import Vue from 'vue' // 导入vuex import Vuex from 'vuex' //挂载Vuex Vue.use(Vuex) //创建Vuex对象 const store = new Vuex.Store({ state:{ products:[ {name:"小米手机",price:"5000",country:"CN"}, {name:"苹果手机",price:"6000",country:"US"}, {name:"魅族手机",price:"4000",country:"CN"}, {name:"Vivo手机",price:"3000",country:"CN"} ] }, getters:{ // Getter 接受 state 作为其第一个参数,过滤出中国品牌手机: cnProducts:state => { return state.products.filter(product => product.country == 'CN'); } } }) // 默认导出store export default store
2、在components
目录下,新建Product.vue
:
<template> <div> <ul> <li v-for="(item,i) in cnProducts" :key="i"> 商品名:{{item.name}},价格:{{item.price}},国家:{{item.country}} </li> </ul> </div> </template> <script> export default { computed:{ cnProducts(){ // Getter 会暴露为 $store.getters 对象,可以以属性的形式访问这些值: return this.$store.getters.cnProducts; } } } </script> <style scoped> </style>
$store.getters
对象,可以以属性的形式访问这些值,getter 在通过属性访问时是作为 Vue 的响应式系统的一部分缓存其中的3、修改
App.vue
如下:
<template> <div id="app"> <Product></Product> </div> </template> <script> import Product from './components/Product.vue' export default { name: 'app', components: { Product } } </script> <style> </style>
你也可以通过让 getter 返回一个函数,来实现给 getter 传参。在你对 store 里的数组进行查询时非常有用。
1、修改
store\index.js
中Store
对象如下:
//创建Vuex对象 const store = new Vuex.Store({ state:{ products:[ {name:"小米手机",price:"5000",country:"CN"}, {name:"苹果手机",price:"6000",country:"US"}, {name:"魅族手机",price:"4000",country:"CN"}, {name:"Vivo手机",price:"3000",country:"CN"} ] }, getters:{ // Getter 接受 state 作为其第一个参数,过滤出中国品牌手机: getProductByCountry:state =>(country)=> { return state.products.filter(product => product.country == country); } } })
2、修改Product.vue的computed如下:
computed:{ cnProducts(){ // Getter 会暴露为 $store.getters 对象,可以以方法的形式访问这些值: return this.$store.getters.getProductByCountry("CN"); } }
3、测试结果还是一样
1、mutations
是操作state
数据的方法的集合,比如对该数据的修改、增加、删除等等。我 们之前说过在组件中不能直接修改state,更改 Vuex 的 store
中的状态的唯一方法是提交 mutation
。Vuex 中的 mutation
非常类似于事件:每个 mutation
都有一个字符串的 事件类型 (type) 和 一个 回调函数 (handler)。这个回调函数就是我们实际进行状态更改的地方,并且它会接受 state
作为第一个参数。
2、mutations
方法都有默认的形参:([state] [,payload])
state
是当前VueX对象中的statepayload
是该方法在被调用时传递参数使用的
1)将小米手机名称改为华为手机
修改
store\index.js
中Store
如下:
//创建Vuex对象 const store = new Vuex.Store({ state:{ products:[ {name:"小米手机",price:"5000",country:"CN"}, {name:"苹果手机",price:"6000",country:"US"}, {name:"魅族手机",price:"4000",country:"CN"}, {name:"Vivo手机",price:"3000",country:"CN"} ] }, getters:{ // Getter 接受 state 作为其第一个参数,过滤出中国品牌手机: getProductByCountry:state =>(country)=> { return state.products.filter(product => product.country == country); } }, mutations:{ // 将小米手机更名为华为手机 change(state){ state.products[0].name="华为手机"; } } })
2)将Product.vue
修改如下:
<template> <div> <ul> <li v-for="(item,i) in cnProducts" :key="i"> 商品名:{{item.name}},价格:{{item.price}},国家:{{item.country}} </li> </ul> <button @click="handleClick">更改</button> </div> </template> <script> export default { methods:{ handleClick(){ // 通过$store.commit调用mutations中方法 this.$store.commit("change"); } }, computed:{ cnProducts(){ // Getter 会暴露为 $store.getters 对象,可以以属性的形式访问这些值: return this.$store.getters.getProductByCountry("CN"); } } } </script> <style scoped> </style>
3)测试点击按钮:
4)在实际生产过程中,会遇到需要在提交某个mutations
时需要携带一些参数给方法使用,即 mutation
的 载荷(payload)。
a)单个值提交时:
修改store\index.js
中mutations
如下:
mutations:{ // 将小米手机更名为某手机 change(state,playload){ state.products[0].name=playload; } }
修改Product.vue
中handleClick
如下:
handleClick(){ // 通过$store.commit调用mutations中方法,传递单个参数 this.$store.commit("change","OPPO手机"); }
测试:
b)多个值组装成对象提交:
修改store\index.js
中mutations
如下:
mutations:{ // 将小米手机更名为某手机 change(state,playload){ // state.products[0] = playload;这样写页面不会跟着变 state.products.splice(0,1,playload); } }
修改Product.vue
中handleClick
如下:
handleClick(){ // 通过$store.commit调用mutations中方法,提交对象 this.$store.commit("change",{name:"OPPO手机",price:7000,country:"CN"}); }
测试:
c)另一种提交方式
修改Product.vue
中handleClick
如下:
handleClick(){ // 通过$store.commit调用mutations中方法,提交对象 this.$store.commit({ type:"change", phone:{name:"OPPO手机",price:7000,country:"CN"} }); }
修改store\index.js
中mutations
如下:
mutations:{ // 将小米手机更名为某手机 change(state,playload){ // playload接收的commit提交的整个对象,type和phone都是其中属性 state.products.splice(0,1,playload.phone); } }
测试效果一样。
5)Mutation 需遵守 Vue 的响应规则
为了配合Vue的响应式数据,我们在Mutations
的方法中,应当使用Vue提供的方法来进行操作。如果使用delete
或者xx.xx = xx
的形式去删或增,则Vue不能对数据进行实时响应。
a)Vue.set
为某个对象设置成员的值,若不存在则新增
例如对state
对象中添加一个title
成员:
在store\index.js
中mutations
新增如下方法:
// 给state添加属性 addTitle(state){ Vue.set(state,"title","手机列表"); }
修改Product.vue
中template
如下:
<template> <div> <h1>{{$store.state.title}}</h1> <ul> <li v-for="(item,i) in cnProducts" :key="i"> 商品名:{{item.name}},价格:{{item.price}},国家:{{item.country}} </li> </ul> <button @click="handleClick">更改</button> </div> </template>
修改Product.vue
中handleClick
如下:
handleClick(){ // 通过$store.commit调用mutations中方法,添加属性 this.$store.commit("addTitle"); }
测试:
b)Vue.delete
删除成员,
例如删除state
对象中的title
成员:Vue.delete(state,“title”);
在此就不做演示了。
由于直接在mutation
方法中进行异步操作,将会引起数据失效。所以提供了Actions
来专门进行异步操作,最终提交mutation
方法。
Actions
中的方法有两个默认参数:
context
上下文(相当于箭头函数中的this)对象payload
挂载参数
我们在第三大点中案例中第
4)
小点c
部分将小米手机改为OPPO手机,现在们希望点击更改按钮后果两秒再去执行change
方法,由于setTimeout
是异步操作,所以需要使用actions
。修改
store\index.js
中mutations
和actions
方法如下:
mutations:{ change(state,playload){ state.products.splice(0,1,playload.phone); } } actions:{ // 两秒后提交change asyncChange(context,palyload){ // playload接收的使整个对象 setTimeout(()=>{ context.commit("change",palyload); },2000); } }
在组件中调用,修改Product.vue
中handleClick
方法如下:
handleClick(){ // 通过$store.dispatch调用actions中方法 this.$store.dispatch({ type:"asyncChange", phone:{name:"OPPO手机",price:7000,country:"CN"} }); }
测试发现点击按钮,2秒后成功更改。
1、当应用变得非常复杂时,store 对象就有可能变得相当臃肿。可以采用模块化管理模式。Vuex 允许我们将 store
分割成模块(module)。每个模块拥有自己的 state
、mutations
、actions
、getters
、甚至是嵌套子模块——从上至下进行同样方式的分割。
const moduleA = { state: () => ({ ... }), mutations: { ... }, actions: { ... }, getters: { ... } } const moduleB = { state: () => ({ ... }), mutations: { ... }, actions: { ... } } const store = new Vuex.Store({ modules: { a: moduleA, b: moduleB } }) store.state.a // -> moduleA 的状态 store.state.b // -> moduleB 的状态
2、组件内调用模块a
的状态:
this.$store.state.a
而commit
提交或者dispatch
某个方法和以前一样,会自动执行所有模块内的对应type
的方法:
this.$store.commit('change') this.$store.dispatch('asyncChange')
3、模块的局部状态
1)对于模块内部的 mutation
和 getter
,接收的第一个参数是模块的局部状态对象。
const moduleA = { state: () => ({ count: 0 }), mutations: { increment (state) { // 这里的 `state` 对象是模块的局部状态 state.count++ } }, getters: { doubleCount (state) { return state.count * 2 } } }
2)同样,对于模块内部的 action
,局部状态通过 context.state
暴露出来,根节点状态则为 context.rootState
:
const moduleA = { // ... actions: { incrementIfOddOnRootSum ({ state, commit, rootState }) { if ((state.count + rootState.count) % 2 === 1) { commit('increment') } } } }
3)对于模块内部的 getter,根节点状态会作为第三个参数暴露出来:
const moduleA = { // ... getters: { sumWithRootCount (state, getters, rootState) { return state.count + rootState.count } } }
Vuex 并不限制你的代码结构。但是,它规定了一些需要遵守的规则:
- 应用层级的状态应该集中到单个 store 对象中。
- 提交 mutation 是更改状态的唯一方法,并且这个过程是同步的。
- 异步逻辑都应该封装到 action 里面。
只要你遵守以上规则,如何组织代码随你便。如果你的 store 文件太大,只需将 action、mutation 和 getter 分割到单独的文件。
对于大型应用,我们会希望把 Vuex 相关代码分割到模块中。下面是项目结构示例:
请参考购物车示例
但也有人习惯store中目录结构如下:
store:. │ actions.js │ getters.js │ index.js │ mutations.js │ mutations_type.js ##该项为存放mutaions方法常量的文件,按需要可加入 │ └─modules Astore.js
对应的内容存放在对应的文件中,和以前一样,在index.js
中存放并导出store
。state
中的数据尽量放在index.js
中。而modules
中的Astore
局部模块状态如果多的话也可以进行细分。