VUEX 快速上手

vuex 是什么

vuex 是专门为了vue.js诞生的状态管理的插件或者说是模式,他非常便捷的解决了组件之间数据通讯,尤其是非父子关系的组件,否则我们只能使用公交总线的模式。
它的理念就是统一一个,也是唯一一个仓库 store。 中文API官方点这里跳转

-公交总线模式
VUEX 快速上手

  • vuex提出的模式

VUEX 快速上手
其中框子里的内容就是Vuex的核心 Vuex的核心总结下来就五个词 State Getter Mutation Action Module
我们来一一介绍在这之前我们要先安装Vuex,并引入使用并且实例化

npm install vuex --save
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const store = new Vuex.Store({
	state: {
	.....
	},
	mutations: {
	.......
	},
	actions:{
	......
	}
});
new Vue({
  el: '#app',
  store,
  render: h => h(App)
})

单一状态树
Vuex 使用单一状态树——是的,用一个对象就包含了全部的应用层级状态。至此它便作为一个“唯一数据源 (SSOT)”而存在。这也意味着,每个应用将仅仅包含一个 store 实例。单一状态树让我们能够直接地定位任一特定的状态片段,在调试的过程中也能轻易地取得整个当前应用状态的快照。
单状态树和模块化并不冲突——在后面的章节里我们会讨论如何将状态和状态变更事件分布到各个子模块中。
这样我们就完成了准备工作 ,接下来我们结合vue-cli实例一个一个来看
大体项目路径VUEX 快速上手

//app.vue
<template>
    <div class="container">
        <div class="row">
            <div class="col-xs-12 col-sm-8 col-sm-offset-2 
            col-md-6 col-md-offset-3">
                <h1>Vuex</h1>
                <app-result></app-result>
                <app-another-result></app-another-result>
                <hr>
                <app-counter></app-counter>
                <br/>
                <app-another-counter></app-another-counter>
            </div>
        </div>
    </div>
</template>

<script>
    import Counter from './components/Counter.vue';
    import Result from './components/Result.vue';
    import AnotherResult from './components/AnotherResult.vue';
    import AnotherCounter from './components/AnotherCounter.vue';

    export default {
        components: {
            appCounter: Counter,
            appResult: Result,
            appAnotherResult: AnotherResult,
            appAnotherCounter: AnotherCounter
        }
    }
</script>
//main.js
import Vue from 'vue'
import App from './App.vue'
import {store} from './store/store';
new Vue({
  el: '#app',
  store,
  render: h => h(App)
})

注意,这里只从应用层上来讲解,以下就是博主的总结

  1. 设置状态在state
  2. 获取内容在getters
  3. 更新数据在mutations
  4. 异步操作在actions
  5. 模块拆分modules
    附上小图一张,希望对大家有帮助 看不懂就先看下我后面的分段介绍
    VUEX 快速上手
    首先,我们先设置下store.js
  • 设置状态在state
//这三行后续就省略了
import Vue from 'vue';
import Vuex  from 'vuex';
Vue.use(Vuex);
//这三行后续就省略了
export const store = new Vuex.Store({
	state:{
		counter: 0,
		value: 0
	}
	//就这样我们就在设置了两个个大家都可以获取的状态
})
  • 获取内容在getters
export const store = new Vuex.Store({
	state:{
		counter: 0,
		value: 0
	},
	//根据官方文档首先我们在store的实例对象里增加getters 这里我们可以对state.counter进行运算
	getters:{
		doubleCounter: state =>{
			return state.counter * 2;
		},
		stringCounter: state =>{
			return state.counter + ' clicks';
		}
		......省略 下面可能有很多
	}
})
//编写组件  Result
//注意这设计属性计算最适合的就是computed 
<template>
    <p>Counter is: {{ counter }}</p>
</template>
<script>
    export default {
    //因此我们在computed中设置一个来获取
    //就这样我们在store中设置的状态就可以在组件中获取  十分简单
       computed:{
       	counter(){
       		return this.$store.getters.doubleCounter;
       	}
       }
    }
</script>

但这时候我们深入思考,上方设置getters时候我写了…如果有很多很多,难道我们都要在computed中一个一个写对应的吗?那也太麻烦了,因此诞生了mapgetters 也就是getters的映射 。看栗子

 //编写组件AnotherResult
<template>
	<div>
		<!-- mapGetters模式 -->
		//此处就可以直接获取这两个值,是不是非常方便
		<p>Counter is: {{ doubleCounter }}</p>
		<p>Number of Clicks: {{ stringCounter }}</p>
</div>
</template>
<script>
	//从vuex引入mapGetters
	import { mapGetters } from 'vuex';
    export default {
    	computed:{
    	//展开运算符展开,这样写我们发现省了好多代码,而且上面也可以直接使用,注意此处为ES6中省略写法,键值一样可省略
	    		...mapGetters([
				'doubleCounter',
				'stringCounter'
	    	])
    	}
    }
</script>
  • 更新数据在mutations
  • 异步操作在actions
    看图说话
    我们把这两个放在一起说,首先我们要明确真正去改变state的是mutations,而actions只是向mutations提交请求,那为什么要多次一举呢,因为在mutations中我们只能同步模式操作,而在actions中可以是异步,因此其实我们可以理解为actions是mutations对数据进行修改的代理。但博主这里是习惯全部组件都用actions而不直接使用mutations,原理基本相同,具体可以看API,其实也就关键字不同…来个栗子
//store.js
import Vue from 'vue';
import Vuex  from 'vuex';
Vue.use(Vuex);
export const store = new Vuex.Store({
	state:{
		counter: 0,
		value: 0  // 设置value初始状态值
	},
	getters:{
		doubleCounter: state =>{
			return state.counter * 2;
		},
		stringCounter: state =>{
			return state.counter + ' clicks';
		},
	},
	mutations:{
	//其中payload来接收actions的传参
		increment: (state,payload) => {
			state.counter += payload;
		},
		decrement: (state,payload) =>{
			state.counter -= payload;
		},
	},
	actions:{
	//这里我写了同步和异步(定时器模拟)两种 来证实actions 中可以用异步
	//其中payload来接收传参
	//以下两个对应直接传参 具体可参考组件
	//实际上actions最后还是是向mutations发出请求,只不过这个过程可以在actions中来进行异步操作
	//其中参数都是固定的
	//小贴士我们尽量将所有的这些名字都同意不要自己更改,便于日后维护  (包括mutations中和actions中以及组件事件中)
		increment: ({ commit },payload ) =>{
			commit('increment',payload);
		},
		decrement: ({ commit },payload) => {
			commit('decrement',payload);
		},
	//以下对应对象传参 具体可参考组件	
		asyncIncrement: ({ commit },payload) => {
			setTimeout(()=>{
				commit('increment',payload.by);
			},payload.duration);
		},
		asyncDecrement: ({ commit },payload) => {
			setTimeout(()=>{
				commit('decrement',payload.by);
			},payload.duration);
		},
	}
})
//counter组件  
<template>
    <div>
    	<!-直接传参,在事件后传参数就好-->
        <button class="btn btn-primary" @click="increment(100)">Increment</button>
        <button class="btn btn-primary" @click="decrement(50)">Decrement</button>
    </div>
</template>
<script>
//类比 拥有与getters一样的映射  来极大程度上节省我们书写代码的难度
    import { mapActions } from 'vuex';
   export default {
        methods:{
            ...mapActions([
                'increment',
                'decrement'
            ])     
        }
   }
</script>
//组件AnotherCounter
//对象传参 在时间后可以传参
<template>
    <div>
        <button class="btn btn-primary" @click="asyncIncrement({by:50,duration:500})">Increment</button>
        <button class="btn btn-primary" @click="asyncDecrement({by:100,duration:1000})">Decrement</button>
    </div>
</template>
<script>
    import { mapActions } from 'vuex';
   /*
   利用spread及mapActions方式进行对象传递与引用
    */
   export default {
        methods:{
            ...mapActions([
                'asyncIncrement',
                'asyncDecrement'
            ])
        }
   }
</script>
  • 模块拆分modules
    首先我们明确vuex中我们只有一个Store,如果我们以后的结构功能组建复杂以后,我们把所有的状态,方法都放在Store中,会让这个实例变得异常臃肿,日后维护起来也是十分困难,因此官方给我们提供了一个模块化拆分的模式,modules,最简单的就是我们有几个功能组件就对拆分几个store的模块,假设我先假设我们拥有一个counter组件 ,如何在store中将其拆分。看栗子
    先定义模块
//先写一个Store模块counter.js 格式也可以参考官方实例  本人习惯这种格式
const state = {
	counter: 0
};
const getters = {
	doubleCounter: state =>{
		return state.counter * 2;
	},
	stringCounter: state =>{
		return state.counter + ' clicks';
	}
};
const mutations = {
	increment: (state,payload) => {
		state.counter += payload;
	},
	decrement: (state,payload) =>{
		state.counter -= payload;
	}
};
const actions = {
	increment: ({ commit },payload ) =>{
		commit('increment',payload);
	},
	decrement: ({ commit },payload) => {
		commit('decrement',payload);
	},
	asyncIncrement: ({ commit },payload) => {
		setTimeout(()=>{
			commit('increment',payload.by);
		},payload.duration);
	},
	asyncDecrement: ({ commit },payload) => {
		setTimeout(()=>{
			commit('decrement',payload.by);
		},payload.duration);
	}
}
//对象形式暴露 其中内容,ES6省略写法
export default {
	state,
	getters,
	mutations,
	actions
}

加载模块

//引入我们写的counter.js
import counter  from './modules/counter/counter';
Vue.use(Vuex);
export const store = new Vuex.Store({
	state:{
		value: 0
	},
	//非常简单 只需要在modules下写一下就行
	modules: {
		counter
	}
})

Ok 就这么简单 以后我们有需要可以自己写任意的模块,只要暴露接口,最后在Store中使用即可
以上为简单实例,基本满足我们常规VUE项目的使用,具体还可以参考官方实例,例如命名空间等,有问题可以留言

补充

我们在表单中使用v-model却不能直接双向数据绑定vuex的state 会报错 这时候我们需要借鉴双向数据绑定的原理Object.defineProperty() 里的get() 和set() 看栗子

<input v-model="message">
// ...
computed: {
  message: {
    get () {
      return this.$store.state.obj.message
    },
    set (value) {
      this.$store.commit('updateMessage', value)
    }
  }
}

这样我们就实现了表单双向数据绑定