Vuex入门

153 阅读5分钟

Vuex

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。

为什么需要Vuex?

在 Vue 应用中,通常使用单向数据流,父组件将状态传递给子组件,这样可以很好地管理数据流动。然而,当遇到多个组件需要共享状态时,这种简单的单向数据流变得难以维护。具体问题如下:

  1. 多个视图依赖同一状态
    • 当多个组件依赖于相同的状态时,通过父组件传参会非常繁琐,特别是组件嵌套层次较深时,需要层层传递数据。
    • 对于兄弟组件间的状态传递,单向数据流也无法直接满足需求,因为状态通常只能在父组件和子组件之间传递,而兄弟组件没有直接的共享机制。
  2. 来自不同视图的行为需要变更同一状态
    • 在复杂应用中,不同视图可能会触发同一状态的更改。常见的做法是让父子组件直接引用状态或通过事件触发同步状态,但这样会导致状态的多份拷贝,增加了状态管理的复杂性。
    • 如果不小心处理,容易出现不同组件的状态不一致,导致维护成本增加,代码变得难以理解和维护。
Vuex的优势

为了解决以上问题,Vuex 提供了一种全局状态管理的方式,将共享状态抽取出来,用一个全局单例来管理。这种模式下:

  • 组件树可以作为一个“巨大的视图”:任意组件都可以访问或改变全局状态,不再受限于层层传递数据。
  • 状态与视图分离:Vuex 通过定义严格的状态管理规则,保持状态与视图的独立性,避免了直接操作状态带来的不可维护性。

通过 Vuex,应用的代码结构更加清晰,状态流动也更具规律性,因此在组件间共享状态时更易维护和扩展。

第一个Vuex实例

每一个 Vuex 应用的核心就是 store(仓库)。“store”基本上就是一个容器,它包含着你的应用中大部分的状态 (state)

Vue.use(Vuex)
const store = new Vuex.Store({
    state: {
        count: 0
    },
    mutations: {
        increment(state){
            state.count++
        }
    }
})

//触发state变更和获取state
state.commit('increment')
console.log(store.state.count) // 输出1
  1. 响应式状态存储
    • Vuex 的状态存储是响应式的。当 Vue 组件从 Vuex 的 store 中读取状态时,如果该状态发生了变化,Vuex 会自动通知相关的组件进行高效更新。这种响应性在组件中使用全局变量时无法实现,因为普通的全局变量并不会触发视图的自动更新。
  2. 状态更改受控
    • 在 Vuex 中,不能直接修改状态。修改状态的唯一方法是通过提交 (commit) mutation,这提供了一个严格的状态变更管理流程。通过 mutation 的集中处理,可以方便地跟踪所有状态的变化记录,为调试和维护带来了极大的便利。而普通的全局变量可以在任何地方任意修改,导致状态的变化不可追踪,增加了维护难度。

State

由于Vuex使用单一状态树,单个对象包含了应用的全部层级状态,所以每个应用仅仅包含一个store

如果有些状态严格属于单个组件,最好还是作为组件的局部状态

在Vue组件中获取state

在根实例(如App.vue)中注册store并在main.js中引入,子组件可以通过this.$store来访问

const Counter = {
  template: `<div>{{ count }}</div>`,
  computed: {
    count () {
      return this.$store.state.count
    }
  }
},
handleAdd(){
      this.$refs.handleForm.form = {
        createName: this.$store.state.thAuth.user.userInfo.userAlias,
      };
    },
mapState辅助函数

一个组件要获取多个state的时候,每个都在computed中写return方法有些重复。mapState函数帮助我们设置计算属性

// store.js
export const store = new Vuex.Store({
  state: {
    user: {
      name: 'Alice',
      age: 30
    },
    projects: ['Project A', 'Project B']
  }
});

// Component.vue
<template>
  <div>
    <p>用户:{{ userName }}</p>
    <p>年龄:{{ userAge }}</p>
    <p>项目列表:</p>
    <ul>
      <li v-for="project in projects" :key="project">{{ project }}</li>
    </ul>
  </div>
</template>

<script>
import { mapState } from 'vuex';

export default {
  computed: {
    // 使用 mapState 将 store 中的状态映射为本地计算属性
    ...mapState({
      userName: state => state.user.name,   // 映射为计算属性 userName
      userAge: state => state.user.age,     // 映射为计算属性 userAge
      projects: state => state.projects     // 映射 projects 数组
    })
  }
};
</script>

注:...为展开运算符,相当于手动写三个属性的return方法 当映射的计算属性的名称与 state 的子节点名称相同时,可以直接给mapState传一个字符串数组:...mapState(['user','projects'])

Getter

相当于给store设置computed属性,也是一样有缓存属性

const store = new Vuex.Store({
  state: {
    todos: [
      { id: 1, text: '...', done: true },
      { id: 2, text: '...', done: false }
    ]
  },
  getters: {
    //接受state为第一个参数
    doneTodos: state => {
      return state.todos.filter(todo => todo.done)
    },
    //可以接收另一个getter作为第二参数
    doneTodosCount: (state, getters) => {
        return getters.doneTodos.length
    },
    //这里使用的是嵌套函数
    getTodoById: (state) => (id) => {
    	return state.todos.find(todo => todo.id === id)
  	}
  }
})
import { mapGetters } from 'vuex'
export default {

	//通过属性获取
	this.$store.getters.doneTodos//doneTodos执行结果
	this.$store.getters.doneTodosCount//值为1
	//通过方法获取
	this.$store.getters.getTodoById(2)//这个时候不会缓存结果
    computed: {
        ...mapGetters(
        	['doneTodosCount','anotherGetter']
        )
        //如果你想换个名字可以写成对象
        ...mapGetters({
            doneCount: 'doneTodosCount'
        })
	}
}

Mutation

更改store的状态的唯一方法是提交mutation,每个mutation都有一个字符串类型的type和handler,handler接收state作为第一个参数

const store = new Vuex.Store({
    state:{
        count: 1
    },
    mutations:{
        increment(state){
            state.count++
        }
    }
})
//用相应的字符串类型type调用commit方法
store.commit('increment')

mutation的handler可以接受额外的参数payload载荷,为了确保可读性,载荷应该是一个对象

mutations: {
    increment(state, payload){
        state.count += payload.amount
    }
}
//提交时传入payload
store.commit('increment',{
    amount: 10
})
//也可以直接提交一个有type的payload
store.commit({
    type: 'increment',
    amount: 10
})

由于 Vuex 的 store 状态是响应式的,因此任何状态变更都会触发监视该状态的 Vue 组件自动更新。但为了确保这种响应式更新能够正常工作,mutation 在操作状态时需要遵循一些 Vue 的响应式规则

提前在state初始化所有要用到的属性(比如上文中的state.count)

动态添加新属性,比如使用Vue.set或者对象展开运算符

const mutations = {
    setUserAddress(state, address){
        //要在state.user上添加新属性,有两种方法
        Vue.set(state.user, 'address', address)
        //使用展开符
        state.user = { ...state.user, address: address }
    }
}

在组件中可以使用mapMutations来映射,相当于把this.$store.commit()映射到组件里的方法

export default {
    methods: {
        //把this.$store.commit('increment')映射到this.incrementCount方法上
        ...mapMutations({
            incrementCount: 'increment',
            decrementCount: 'decrement'
        })
        //如果两个方法名字不改,可以用字符串数组
        ...mapMutations(['increment', 'decrement'])
    }
}

在mutation中使用异步调用会使得程序难以调试——不知道异步方法什么时候调用,这与mutation的追踪state变化记录的设计目的背道而驰,所以mutation都是同步的

Action

但是实际开发中,异步调用的需求肯定是会遇到的,Action应运而生。Action同样提交的是mutation来修改状态,但是可以异步。

这样确保了异步操作的控制权归 action 所有,而 mutation 只负责具体的状态更新,保持同步特性。

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

context是一个和store实例有相同方法和属性的对象,所以能通过context.commit来提交mutation,context.statecontext.getters来获取

分发Action
actions:{
    //用到了ES2015的参数解构,直接把context.commit获取出来,所以下面commit的时候不用写context
    incrementAsnyc({ commit }){
        setTimeout(() => {
            commit('increment')
        }, 1000)
    }
}

store.dispatch('incrementAsnyc')
//和Mutation一样可以有payload
store.dispatch('incrementAsnyc',{ amount: 10 })
store.dispatch({
    type: 'incrementAsync',
    amount: 10
})
export default {
    methods: {
        //把this.$store.dispatch('increment')映射到this.incrementCount方法上
        ...mapActions({
            incrementCount: 'increment',
            decrementCount: 'decrement'
        })
        //如果两个方法名字不改,可以用字符串数组
        ...mapActions(['increment', 'decrement'])
    }
}
组合Action

有的时候需要处理复杂的异步逻辑,这个时候要用到组合Action

action: {
    actionA({commit}){
        return new Promise((resolve, reject)=>{
            setTimeout(()=>{
                commit('mutation')
                resolve()
            }, 1000)
        })
    }
}
//这个时候,你可以在分发或者另一个action中处理actionA的回调
store.dispatch('actionA').then(()=>{})
action:{
    actionB({ dispatch, commit }){
        //等待A完成后处理回调,在回调里提交另一个mutation
        return dispatch('actionA').then(()=>{
            commit('anotherMutation')
        })
    }
}

Module

避免store过于臃肿,可以分割为不同的模块,每个模块有自己的state,mutation,action,getter

const store = new Vuex.Store({
  modules: {
    app,
    tagsView,
    settings,
    msgPop,
    systemData
  },
  getters
})

对于模块内的action,通过context.rootState可以获取根的state