Vuex状态管理模式

多个组件依赖或修改同一个状态 - 共享
使用Vue.observable可以实现一个轻量型的状态管理

  1. 基础
  2. Modules

1 基础

  1. 基本结构与使用
  2. State
  3. Mutations
  4. Actions
  5. Gtters
  6. map

1.1 基本结构与使用

结构图
image.png

安装Vuex

npm install vuex --save

store.js文件

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export const store = new Vuex.Store({
    mutations: {
        increment() {
            this.state.count++
        },
        decrement() {
            this.state.count--
        }
    },
    state: {
        count: 0
    }
})

组件一

<template>
  <div>
    <button @click="add">+</button>
    <span>{{ num }}</span>
  </div>
</template>

<script>
import { store } from "../store";
export default {
  store: store,
  computed: {
    num() {
      return store.state.count;
    },
  },
  methods: {
    add() {
      store.commit("increment");
    },
  },
};
</script>

<style>
</style>
//main.js
import { store } from './store'
new Vue({
  store,
  render: h => h(App),
}).$mount('#app')
//组件二
dec() {
  this.$store.commit("decrement");
},

两个组件可以操作同一个值

  • 在store文件中引入Vue以及Vuex
  • Vue.use(Vuex),new Vuex.Store({})
  • 在组件中引入store并注册,可以直接使用store
  • 根实例中引入并注册store,子组件可以通过vm.$store来访问与修改数据

1.1 State

存储状态
每个数据作为对象属性供各个组件访问
通常会使用计算属性让状态能在组件中使用,当状态变化时会重新计算

1.2 Mutations

更改Vuex的store中状态的唯一方法是提交mutation

  • mutation中的回调函数接收state作为第一个参数

    mutations: {
      increment (state) {
          state.count++
      }
    }
  • 通过store.commit来声明使用哪个回调处理数据,可以传递额外的参数

    mutations:{
      increment(state,n){
          state.count +=n
      }
    }
    //提交
    store.commit('increment',10)

    参数为一个对象

    mutations: {
    increment (state, payload) {
      state.count += payload.amount
    }
    }
    //提交
    store.commit('increment',{amount:10})
  • 对象形式的提交

    //处理的回调与参数时对象的相同
    mutations: {
    increment (state, payload) {
      state.count += payload.amount
    }
    }
    //整体提交一个对象
    store.commit({type:'increment',amount:10})
  • 如果需要添加状态,可以使用Vue.set(obj,key,val),或者使用扩展符解构赋值

    //提交添加状态
    addOne() {
    store.commit("addState", { name: "张三" });
    },
    //添加状态的回调
    addState(state, obj) {
      const keys = Object.keys(obj)
      Vue.set(state, keys[0], obj[keys[0]])
    }
Mutation必须是同步函数,异步操作时不能判定是哪个操作在改变状态

1.3 Actions

类似于mutation,不同如下:

  • 提交的是mutation,不直接更改状态
  • 可以包含异步操作

store

actions: {
    getData(context, param) {
        const oneData = [param, '数据1', '数据2']
        context.commit('GETDATA', oneData)
    }
},
mutations: {
    GETDATA(state, data) {
        Vue.set(state, 'myData', data)
    },
},
state: {
    myData: [],
}

组件

<template>
  <div>
    <button @click="getItem">请求数据</button><br />
    <span v-for="(item, index) in myData" :key="index">{{ item }}</span>
  </div>
</template>

<script>
export default {
  computed: {
    myData() {
      return this.$store.state.myData;
    },
  },
  methods: {
    getItem() {
      this.$store.dispatch("getData", "请求");
    },
  },
};
</script>
  • Action的回调函数接收一个与store实例具有相同属性和方法的context对象作为第一个参数

    context !== this,context并不是store实例本身,mutation的state参数则是store实例上的真实的属性
    在action中主要使用contextcommit方法去提交mutation,所以可以使用解构赋值的方式简化参数,可以读state中的数据

    actions:{
        getData({commit},param){
            commit('GETDATA',parm)
        }
    }
  • Action通过store.dispatch进行分发,如果回调函数返回一个Promise,可以在分发的后面进行链式操作或者在其他action中进行链式操作

    actions:{
      actionA({commit}){
        return new Promise(async (resolve,reject)=>{
            await delay();
            commit('mutation1');
            resolve()
        })
      },
      actionB({dispatch,commit}){
          return dispatch('actionA').then(()=>{
              commit('mutation2')
          })
      }
    }
  • Action中可以异步请求与操作,在不同的异步阶段提交mutation,可以利用async/await组合action

1.4 Getters

类似与计算属性
在使用state中数据的同时,可能还需要由state派生出的一些状态,这时可以在组件内使用计算属性对state进行操作
但是当多个组件需要同样的派生状态,部分组件需要原state时,在每个组件内都使用计算属性显然很麻烦
getter可以将state中的数据计算后供组件访问

//store
state: {
    user: [
        { name: '张三', age: 18 },
        { name: '李四', age: 15 },
        { name: '王二', age: 21 },
        { name: '麻子', age: 17 },
    ]
},
getters: {
    adult({ user }) {
        return user.filter(item => {
            return item.age >= 18
        })
    }
}
//组件内
computed: {
  adultUser() {
    return this.$store.getters.adult;
  },
},
  • getters中的回调第一个参数state,第二个参数getters
  • 可以让getter返回一个函数,这样可以向getter中传递数据,配合数组的find方法,可以对store中的数据进行查询

1.5 map

辅助函数

  1. mapState
  2. mapGetters
  3. mapMutations
  4. mapActions
1.5.1 mapState

当一个组件需要多个状态时,频繁使用计算属性让代码很臃肿,使用辅助函数可以简化操作

//store
state: {
  a: 1,
  b: 2
},
//组件
<template>
  <div>
    <span>{{ a }}{{ b }}</span>
  </div>
</template>

<script>
import { mapState } from "vuex";
export default {
  computed: {
    //数组参数
    ...mapState(["a", "b"]),
  },
};
</script>
引入mapState函数,参数为数组形式,但是不能改名,容易与data中的数据发生冲突
mapState函数返回一个对象,使用展开运算符,可以逐个混入到计算属性中
//对象参数
computed: {
  ...mapState({
    a: "a",
    bar: "b",
  }),
},

1.5.2 mapGetters

与mapState函数类似

  • 在组件中引入mapGetters函数
  • 两种传参方式
  • 使用扩展运算符将getter混入computed对象中
  • 相当于计算属性的结果在组件中使用

1.5.3 mapMutations

将组将中的methods映射为store.commit调用

//store
state: {
    count: 0
},
mutations: {
    increment(state, param) {
        state.count += param
    }
}
//组件
<template>
  <div>
    <button @click="add(10)">加10</button>
    <span>{{ num }}</span>
  </div>
</template>

<script>
import { mapMutations } from "vuex";
import { mapState } from "vuex";
export default {
  computed: {
    ...mapState({
      num: "count",
    }),
  },
  methods: {
    ...mapMutations({
      add: "increment",
    }),
  },
};
</script>
  • 在组件中引入mapMutations函数
  • 两种传递参数的方式
  • 扩展运算符将返回的对象混入到methods
  • 相当于在methods中注册的方法,调用时可以传递参数,作为回调函数的第二个参数
1.5.4 mapActions

与mapMutations类似
最终可以在组件中调用,可以使用this灵活的调用

2 Module

当状态较多时,store对象就会变得越来越臃肿
Vuex支持将store对象分割成模块。每个模块拥有自己的state、mutation、action、getter甚至是嵌套子模块

2.1 模块化的导入和导出

目录结构示例

└── store
    ├── index.js          # 我们组装模块并导出 store 的地方
    ├── actions.js        # 根级别的 action
    ├── mutations.js      # 根级别的 mutation
    └── modules
        ├── topics.js       # 购物车模块
        └── comments.js   # 产品模块
//模块1
export const Topics = {
    state: {
        backTopic: '推荐',
        userTopic: '自定义'
    },
    mutations: {
        modify(state, param) {
            state.userTopic = param
        }
    }
}
//模块2有同名的mutation
...
//mutathions.js 有一个同名的方法
...
//index.js
import Vue from 'vue'
import Vuex from 'vuex'
import { Topics } from './modules/topics'
import { Comments } from './modules/comments'
import { mutations } from './mutations'

Vue.use(Vuex)

export const store = new Vuex.Store({
    state: {
        root: '根状态'
    },
    mutations,
    modules: {
        Topics,
        Comments
    }
})
子模块引入在modules对象中,无需改名可以使用简写形式
这样可以分模块操作,根state中会根据引入的模块混入子state对象
如果在组件中直接提交mutation,同名的mutation会同时开始操作
  • 在模块导出时添加namespaced:true,可以区分不同模块的同名操作

    export const Topics = {
      namespaced: true,
      state: {
          backTopic: '推荐',
          userTopic: '自定义'
      },
      mutations: {
          modify(state, param) {
              state.userTopic = param
          }
      }
    }
  • 模块中的action的context中可以接收到跟状态rootState
  • 模块中的getter的参数:state getters rootState rootGetters

2.2 在组件中分发与提交

2.2.1 通过store直接访问
//读取state
computed: {
  userTopics() {
    return this.$store.state.Topics.userTopic;
  },
}
//提交mutation
this.$store.commit("Topics/modify", "修改");
使用命名空间时,需要添加路径才能提交mutation,action亦是如此
2.2.1 使用辅助函数
computed: {
  ...mapState({
    a: (state) => state.Topics.userTopic,
  }),
},
methods: {
  ...mapMutations({
    modiT: "Topics/modify",
  }),
},

对某一个模块的多次引用,可以进行简化

computed: {
  ...mapState("Topics", {
    a: "userTopic",
    b: "backTopic",
  }),
},
methods: {
  ...mapMutations("Topics", {
    modiT: "modify",
  }),
},

createNamespacedHelpers函数

import { createNamespacedHelpers } from 'vuex'

const { mapState, mapMutations } = createNamespacedHelpers('Topics')
此时生成的辅助函数会有一个自定义的路径,可以像一般情况下使用

其他注意事项

  • 在模块中注册全局action

    将回调函数写成对象形式,添加属性root:true,回调写放在handler方法中
  • 动态注册模块

    创建store后使用registerModule函数

    // 注册模块 `myModule`
    store.registerModule('myModule', {
      // ...
    })
    // 注册嵌套模块 `nested/myModule`
    store.registerModule(['nested', 'myModule'], {
     // ...
    })

怼怼
73 声望6 粉丝