Vuex如何使用

359 阅读3分钟

前言

本篇文章将为大家介绍什么是vuex?什么时候使用?该如何使用?vuex的装饰器是什么?

为什么要使用vuex

1、当多个视图依赖同一状态(state--->view)
2、来自不同视图的行为需要变更同一状态(view--通过Actions-->state)
第一种情况的理解:当组件嵌套,可以使用组件传参的方法传递数据,但当多层嵌套时,传参方法this.$emit;prop会变得非常繁琐且不利于维护。
第二种情况的理解:当多个组件的行为(提交表单、下一步),需要更改同一个数据时,父子组件可以直接引用(this.$ref;this.$parent)触发对应的方法并修改数据。但当视图嵌套过于复杂的时候,以上的模式会导致代码难以维护。
所以可以将用到的公共状态提取出来,统一管理。把组件树抽象为一个视图,无论在组件树的任何位置,都可以获取和更改数据。

vuex的基本概念

每个应用仅包含一个store实例,实例中可传入参数(object)state、getters、mutationss、actions、modules

state

抽取的公共状态就存在这里

const store = new Vuex.Store({
    state: { 
        // 定义一个name,以供全局使用 
        name: '张三', 
        // 定义一个number,以供全局使用 
        number: 0, 
        // 定义一个list,以供全局使用 
        list: [ { id: 1, name: '111' }, { id: 2, name: '222' }, { id: 3, name: '333' }, ] 
    }
 });

通常在组件中的计算属性获取state

computed:{
    // 通过return获取state数据
    name(){
        return this.$store.state.name
    }
    // 当state数据较多时,可以使用mapState辅助函数
    ...mapState(['name']),
    // 赋值别名,接收对象
    ...mapState({aliasName: 'name'}),
}

getters

有时需要从state中派生出一些状态,例如数据改造、数组过滤等
简单理解:store中的getters相当于组件中的computed,写法一致

getters: {
        longName (state) {
             return 'stateGetters' + state.name
        },
        getTodoByid: (state) => (id) => {
             return state.todos.find(todo => todo.id === id)
        }
    },
getLongName () {
       return this.$store.getters.longName
    },
    // 同样可以使用展开运算符
    ...mapGetters(['longName']),
    ...mapGetters({aliasLongName: 'longName'}),
    // 可对getters传入参数,getter通过返回函数来获取该参数
    getTodoByid () {
      return this.$store.getters.getTodoByid(2)
    }

mutations

以上讲的是获取state,那么如何更改state数据呢?
唯一的方法就是提交mutation。
但是不能直接操作mutation,需要调用store.commit方法。mutation可接收两个参数(state, 'payload'),

    // ...
    mutations: {
        increment (state) {
          state.count++
        },
        setMyNumber (state, obj) {
          state.count += obj.num
        },
        aNameMutation (state, dataName) {
          state.name = dataName
        }
    }
    methods: {
        changeState () {
          this.$store.commit('setMyNumber', {num: 6})
        },
        // 可以使用mapMutations,映射为this.$store.commit('aNameMutation', '传入参数')
        ...mapMutations(['aNameMutation']),
        // 可以起别名,映射为this.$store.commit('aNameMutation')
        ...mapMutations({add: 'aNameMutation'})
      }

切记mutation中必须使用同步方法,因为每一条 mutation 被记录,devtools 都需要捕捉到前一状态和后一状态的快照。执意写异步也能执行,但后一状态无法被记录。

actions

需要异步操作可以在actions中写。
Actions 和 Mutations 区别

  • Actions 提交的的mutation,而mutation提交的是state
  • Actions可以包含任何异步操作
    actions: {
        selfF (context) {
          console.log(context)
          setTimeout(() => {
            context.commit('increment')
          }, 1000)
        },
        ActionA ({commit}, obj) {
          setTimeout(() => {
             commit('setMyNumber', obj)
          }, 1000)
        }
     }

Actions拥有store实例同样的属性(但不是store的实例),第一个参数context(包含commit、dispatch、getters、state);第二个参数是载荷(传入参数)

a.png
和mutation一样,可以使用mapActions辅助函数。

    // 在组件中使用
    // 提交actions
    touchActions () {
      this.$store.dispatch('selfF')
    },
    // 通过mapActions分发
    ...mapActions(['ActionA']),
    touchActionsA () {
       this.ActionA({num: 10})
    }

因为actions是异步的,并且返回一个promise,所以想要知道action什么时候结束,可以使用.then().catch()来写回调和捕获错误。

modules

当应用越来越复杂时,store会越来越臃肿。所以可以将store分割成模块,每个模块拥有自己的state/getters/mutations/actions,也可以再次嵌套modules。
使用方法:
在index.js统计目录下新建modules文件夹,新增moduleA.js(模块A)

   
const state = () => ({
  moduleAName: '我是模块a的数据',
  work: '工人'
})

// getters
const getters = {
  deriveName (state) {
    // state是moduleA中的state
    return state.moduleAName + '---派生字符串'
  }
}

// actions
const actions = {
  moduleANameAction (context, obj) {
    context.commit('moduleANameMutation', obj)
  }
}

// mutations
const mutations = {
  moduleANameMutation (state, obj) {
    state.moduleAName += obj.string
  }
}
export default {
  namespaced: true,
  state,
  getters,
  actions,
  mutations
}

在index.js中引入

    import moduleA from './modules/moduleA'
    const store = new Vuex.Store({
        // ... 
        modules: {
          moduleA
        }
    })

基础用法(问题):

1、为什么要使用模块?如何使用模块?

为了避免store入口文件过于臃肿和方便管理。新建模块js文件,分别定义state、getters、mutations、actions后暴露出去,并在index.js中modules挂载。

2、在组件中如何使用模块中定义的state、getters、mutations、actions?

若未定义命名空间,直接调用即可。 this.$store.state.xxx
this.$store.getter['type']
this.$store.commit('type')
this.$store.dispatch('type')

3、什么是模块的命名空间?如何使用?使用后有何不同?

默认情况下模块的getters、mutations、actions是注册在全局的,为了避免同名、使组件有更高的复用性封装度,所以使用命名空间。
在模块内添加nameSpaced:true
模块使用命名空间后,写法并没有改变,只是添加了一个属性nameSpaced:true 。不同的地方在组建内用的模块的getter、mutation、action需要按路径匹配 this.$store.getter['moduleA/type']
this.$store.commit['moduleA/type']
this.$store.dispatch['moduleA/type']

4、如何访问全局state、getter?

rootState、rootGetter会作为第三个、第四个参数传入getter。同时也会传入action的context参数对象。

    getters: {
        // ...
        someGetter (state, getters, rootState, rootGetters) {
            // ...
        }
    }
    actions:{
        someAction ({ dispatch, commit, getters, rootState, rootGetters }) {
            // ...
        }
    }

5、 如何在模块内访问全局的mutations、actions?

{root:true}传入commit和dispatch

 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'
 },

6、如何在模块内注册全局的muataion、actions?

    actions: {
        someAction: {
          root: true,
          handler (namespacedContext, payload) { ... } // -> 'someAction'
        }
    }

mutation和action的区别

  • mutation提交的是state;action提交的是mutation
  • mutation只支持同步方法,action支持任何异步写法

装饰器vuex-module-decorators

vuex模块的装饰器,内容同样包含state、getters、mutations、actions,只是写法略有区别。装饰器的主要作用是在组件中帮助用户简便写法,无需this.$sotre.diapatch('namespace/name')写这么一大串了,当然这种写法仍然有效。
定义模块

    import { Module, VuexModule, Mutation, Action, getModule } from 'vuex-module-decorators'
    import store from '@/store'
    type User = {name: string; password: string}
    // 动态模块,只有组件内使用的时候才会注册
    @Module({name: 'moduleB', namespaced: true, dynamic: true, store: store})
    class moduleB extends VuexModule {
      // state
      public loginInfo: User[] = []

      // getter
      get userNumber():number {
        return this.loginInfo.length
      }

      // mutation
      @Mutation
      USERINFO(user: User): void {
        console.log(user)
        this.loginInfo.push(user)
      }

      // action
      // commit两种调用方式:1、在Action后边添加commit,最后return的值即为传入USERINFO的参数
      @Action({ commit: 'USERINFO' })
      ADD_USER_ONE(): User {
        return { name: '张三', password: '123'  }
      }
      // 2、在action中通过this.XXX(mutation名称)调用并传值
      @Action
      ADD_USER_TWO(obj: any): void {
        console.log(obj)
        this.context.commit('USERINFO', obj)
        // this.USERINFO(obj)
      }
    }
    export const moduleBModule = getModule(moduleB)

在组件内使用

  // computed
  get userinfo() {
    return JSON.stringify(moduleBModule.loginInfo)
  }
  mounted() {
    console.log(this.$store)
    console.log(moduleBModule)

  }
  // 提交mutation
  commitMutation() {
    moduleBModule.USERINFO({name: '奥特曼', password: '666'})
  }
  // 提交action不传参
  commitAction() {
    moduleBModule.ADD_USER_ONE()
  }
  // 提交action传参
  commitAction_Two() {
    moduleBModule.ADD_USER_TWO({name: '迪加', password: '23333'})
  }