Vuex

497 阅读6分钟

Vuex是什么

Vuex 是状态管理模式,采用集中式存储管理所有组件的状态 (数据),并以相应的规则保证状态 (数据) 以一种可预测的方式发生变化

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

  • 多个视图依赖于同一状态 ---- 传参对于多层嵌套的组件非常繁琐,无法在兄弟组件间传递
  • 来自不同视图的行为需要变更同一状态 --- 多份拷贝数据的变更和同步通常难以维护

因此,把组件的共享状态抽取出来,以一个全局单例模式管理,在这种模式下,我们的组件树构成了一个巨大的“视图”,不管组件在树的哪个位置,任何组件都能获取状态或者触发行为,通过剥离 组件状态/方法,维持视图和状态间的独立性,代码会变得更 结构化易维护

vuex

什么情况使用Vuex

如果应用够简单,最好不要使用 Vuex,一个简单的 store 模式 就足够了

如果需要构建一个中大型单页应用,为了更好地在组件外部管理状态,Vuex 将会成为自然而然的选择

开始

Vuex 应用的核心是 store ( 仓库 ),“store” 基本上就是一个容器,包含着应用中大部分的状态 (state),Vuex 和单纯的全局对象有以下两点不同:

  • Vuex 的状态存储是响应式的,组件从 store 中读取状态,若 store 中的状态发生变化,组件也会得到高效更新

  • 不能直接改变 store 中的状态,改变 store 中状态的唯一途径就是显式地提交 (commit) mutation,这样可以方便地跟踪每一个状态的变化,从而能够实现一些工具,以便更好地了解应用

通过 vue create project name 创建项目勾选 vuex,或者在现有项目中 vue add vuex 添加

安装完成,main.js 中多了引入和挂载

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store' //导入创建的store

Vue.config.productionTip = false

new Vue({
  router,
  store,//挂载store,在任意组件中访问 this.$store property
  render: h => h(App)
}).$mount('#app')

src目录下多了个 store/index.js

import Vue from 'vue'
import Vuex from 'vuex' //1.导入模块

Vue.use(Vuex) //2.使用当前的插件

export default new Vuex.Store({ //3.创建实例并抛出
  state: { //存储当前的状态
      conte: 0
  },
  mutations: { //声明同步的方法
  },
  actions: { //声明异步的方法
  },
  modules: {
  }
})

在组件中获取状态

store 内声明的属性,可以通过$store.state直接获取,也可以在需要的组件内通过 computed 属性监听获取,显得麻烦但好处是可以向store提交数据

{{$store.state.count}}  //直接获取方法
//--------------------------
computed: {             //计算属性获取
    count() {
        return this.$store.state.count;
    }
}

dispatch & commit 触发修改状态

异步方法

组件中声明方法,通过 dispatch(调用) 触发 actions 中声明的方法来对服务器进行数据交互,实现异步方法

methods: {
    plusOne() { //1.声明方法
        this.$store.dispatch("plusOne");//2.通过dispatch调用actions内的方法
    }
}

事件触发组件中的方法,在 store 的 actions 内的方法被调用后触发 mutations 内的方法来修改当前状态

export default new Vuex.Store({
  state: {
    count: 11//5.状态被更改后,引入此状态的组件都会立即更新
  },
  mutations: {
    plusOne(state) {//4.执行方法并传入state,更改state内的状态
      state.count++
    }
  },
  actions: {
    plusOne({ commit }) {//3.此方法被调用后,通过commit来调用mutations内的plusOne方法
      commit('plusOne')
    }
  },
  modules: {
  }
})

此时当前状态被修改,各个没有任何关系的组件中引入的 {{ $store.state.count }} 都会响应这个状态的更新

同步方法

当前操作不需要对服务器进行数据交互时,可以在组件方法中使用 this.$store.commit('plusOne') 直接触发 mutations 内的方法完成状态更改

Getters属性

  • store 内还有个 getters 属性,类似于 store 内的计算属性,如果多个组件都需要统一的计算属性得出新的状态,可以使用这个属性来处理,就像计算属性一样,getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算,getter 接受 state 作为其第一个参数:

    getters: {
      evenOrOdd(state){
        return state.count % 2 ? '偶数':'奇数'
      }
    }
    
  • Getter 会暴露为 store.getters 对象,你可以以属性的形式访问这些值:

    computed: {
      evenOrOdd() {
        return this.$store.getters.evenOrOdd;
      }
    }
    
  • Getter 也可以接受其他 getter 作为第二个参数:

    getters: {
      doneTodosCount: (state, getters) => {
        return getters.doneTodos.length
      }
    }
    
    store.getters.doneTodosCount // -> 1
    
  • Getter 还可以接收 rootState 作为第三个参数获取整个store的State,通过 rootState.module模块名.state状态名 来获取任意不相关的store的状态

    getters: {
      doneTodosCount: (state, getters, rootState) => {
        return rootState.products.products //第一个product是模块名,第二个是状态名
      }
    }
    

Vuex辅助函数运用

每次引入都需要 this.$store.state.count / this.$store.dispatch("") / this.$store.commit("")就显得非常繁琐

vuex提供了四种辅助函数,分别对 store 中 state / getters / mutations / actions 进行处理

import { mapState,mapGetters,mapMutations,mapActions } from 'vuex';

mapState & mapGetters

接下来只需要在计算属性中使用对应函数就可以获取到想要的状态

computed:{
    ...mapState(['count','username']),
    ...mapGetters(["evenOrOdd"])
}

如果需要引入的属性名 和 store 的状态名不一样可以这样写来对状态进行重命名

computed:{
    ...mapState({
        myCount: 'count',
        user: 'username'
    })
}

mapMutations & mapActions

这两个的获取方式和上面两个一样,区别在于,上面两个获取的是状态,这两个获取的是方法,所以需要在 methods 中获取,就可以直接在事件中使用了

methods: {
    ...mapMutations(['plusOne']),
    ...mapActions(['plusOne'])
}
<button @click='plusOne'> +1 </button>

组件内向store传递数据

方法内传递数据

data() {
    return {
        amount: 10
    }
},
methods: {
	plusOne() {
		this.$store.dispatch("plusOne",this.amount);
	}
}

上面的还有另一种写法:

methods: {
	plusOne() {
		this.$store.dispatch({
            type:"plusOne",
            this.amount
        });
	}
}

辅助函数传递数据

由于辅助函数获取store内的方法,是在事件处直接触发,所以传值也要在事件那里完成,这样反而更简便

<button @click="plusOne(amount)">+1</button>

store获取数据

mutations: {
    plusOne(state, amount) {
        state.count += amount
    }
},
actions: {
	plusOne({ commit }, amount) {
		commit('plusOne', amount)
	}
}

如果传入store的数据是一个对象,可以通过解构赋值获取,在函数内使用

actions: {
    plusOne({ commit }, obj) {
        console.log(obj), //{amount: 10}
        commit('plusOne', obj)
    }
}

传递对象给 mutation,并通过解构赋值获得数据

mutations: {
    plusOne(state, {amount}) {
        state.count += amount
    }
},
actions: {
	plusOne({ commit },obj) {
		commit('plusOne', obj)
	}
}

同步方法便可直接省略actions这一步

Module属性

当应用变得非常复杂时,store 对象就有可能变得相当臃肿

Vuex 允许将 store 分割成模块 ( module ),每个模块拥有自己的 state、mutation、action、getter 甚至是嵌套子模块从上至下进行同样方式的分割:

创建Vuex模块

import vue from 'vue'
import vuex from 'vuex'
import mod1 from './mod1.js' //store的index.js中引入分割后的模块

vue.use(vuex);

export default new vuex.Store({
    modules:{ //配置模块
        mod1
    }
});

mod1.js:在store下新建module,放入其中

export default {
    state:{},
    mutations:{},
    actions:{},
    getters:{}
}

读取模块数据

在组件中通过 $store.state.mod1.count 读取到对应模块内的state

{{$store.state.mod1.count}}

命名空间

默认情况下,模块内部的 action、mutation 和 getter 是注册在全局命名空间的,这样使一个组件内调用 mutation 或 action 时,多个模块能够同时作出响应

如果希望模块具有更高的封装度和复用性,可以通过添加 namespaced: true 的方式使其成为带命名空间的模块,当模块被注册后,它的所有 getter、action 及 mutation 都会自动根据模块注册的路径调整命名,state内的状态已经嵌套了,namespaced不会对其产生影响

export default {
    namespaced: true,
    state:{},
    mutatons:{},
    actions:{},
    getters:{}
}
获取命名空间的方法

通过方法获取:

export default {
    methods: {
		plusOne(){
            this.$store.dispatch("mod1/plusOne");
        }
	}
}

通过辅助函数获取:

import { mapGetters } from 'vuex' //组件内引入辅助函数
export default {
    methods: {
		...mapActions("mod1", ["plusOne"]) //函数内第一个值为vuex模块名,第二个为模块内方法
	}
}