一、vuex的基本使用
1、安装
npm install vuex --save-dev
2、在Vue项目的src文件夹下建一个名为store的文件夹,该文件夹下建一个index.js文件
src/store/index.js
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
// Vuex 的状态存储是响应式的
// 创建一个Vuex.Store的实例
const store = new Vuex.Store({
// 存储状态
state: {},
// 派生状态 类似Vue的计算属性
getters: {},
// 变更状态,更改 store 中状态的唯一方法是提交 mutation
mutations: {},
// 异步变更状态,Action 提交的也是 mutation
actions: {}
});
export default store;
3、在Vue项目的main.js文件中导入store,并把store注册到Vue实例上,就可以将store从根组件“注入”到每一个子组件中,这样任意子组件能通过 this.$store访问到。
import Vue from 'vue';
import App from './App';
// 导入store
import store from './store';
import router from './router';
Vue.config.productionTip = false;
new Vue({
el: '#app',
router,
store, // store注册到Vue实例上
components: { App },
template: '<App/>'
});
二、State
1、state中存储状态
state: {
count: 0,
username: 'liu',
password: 123
}
2、在Vue组件中使用store中存储的状态
<template>
<div>
<!-- 声明为计算属性后使用 -->
<h1>{{ count }}</h1>
<h1>{{ username }}</h1>
<!-- 直接利用$store使用 -->
<h2>{{ $store.state.count }}</h2>
<h2>{{ $store.state.username }}</h2>
</div>
</template>
<script>
export default {
data () {
return {};
},
computed: {
// store中的状态都声明为计算属性
count () {
return this.$store.state.count;
},
username () {
return this.$store.state.username;
}
},
mounted () {
// 声明为计算属性后使用
console.log(this.count);
console.log(this.username);
// 子组件能通过this.$store访问到
console.log(this.$store);
console.log(this.$store.state.count);
console.log(this.$store.state.username);
}
};
</script>
$store是挂载在 Vue 实例上的(即Vue.prototype),而组件也其实是一个Vue实例,在组件中可使用this访问原型上的属性,template 拥有组件实例的上下文,可直接通过{{ $store.state.count }}访问,等价于 script 中的this.$store.state.count。
由于 Vuex 的状态存储是响应式的,从 store 实例中读取状态最简单的方法就是在计算属性中返回某个状态,因此在项目中使用都是先声明为计算属性,再使用
3、mapState辅助函数
当一个组件需要获取多个状态时,将这些状态都声明为计算属性会有些重复和冗余。因此可以利用mapState辅助函数
<template>
<div>
<!-- 声明为计算属性后使用 -->
<h1>{{ count }}</h1>
<h1>{{ username }}</h1>
<h1>{{ password }}</h1>
</div>
</template>
<script>
// 引入mapState函数
import { mapState } from 'vuex';
export default {
data () {
return {};
},
// 利用mapState函数将store中的状态都声明为计算属性
computed: mapState({
count (state) {
return state.count;
},
// 箭头函数使代码更简练
username: state => state.username,
// 传字符串参数 'password' 等同于 `state => state.password`
password: 'password'
}),
mounted () {
// 声明为计算属性后使用
console.log(this.count);
console.log(this.username);
console.log(this.password);
}
};
</script>
上面我们声明的计算属性的名称与 state 的子节点名称相同,因此可以把声明计算属性简化为给 mapState传一个字符串数组。
// 引入mapState函数
import { mapState } from 'vuex';
export default {
data () {
return {};
},
// 利用mapState函数将store中的状态都声明为计算属性
computed: mapState(['count', 'username', 'password']),
mounted () {
// 声明为计算属性后使用
console.log(this.count);
console.log(this.username);
console.log(this.password);
}
};
4、展开运算符+mapState辅助函数(...mapState) 声明计算属性
// 引入mapState函数
import { mapState } from 'vuex';
export default {
data () {
return {};
},
// 利用...mapState将store中的状态都声明为计算属性
computed: {
...mapState(['count', 'username', 'password'])
},
mounted () {
// 声明为计算属性后使用
console.log(this.count);
console.log(this.username);
console.log(this.password);
}
};
三、Getter
1、派生状态
// 创建一个Vuex.Store的实例
const store = new Vuex.Store({
state: {
count: 0,
username: 'liu'
},
// 派生状态
// Getter 接受 state 作为其第一个参数
getters: {
message (state) {
return '当前最新的count值是:' + state.count;
},
// 箭头函数
msg: state => {
return `用户名为:${state.username}`;
}
}
});
2、在组件中使用getters(this.$store.getters.***)
<template>
<div>
<p>{{$store.getters.message}}</p>
<p>{{$store.getters.msg}}</p>
</div>
</template>
<script>
export default {
data () {
return {};
},
mounted () {
console.log(this.$store.getters);
console.log(this.$store.getters.message);
console.log(this.$store.getters.msg);
}
};
</script>
3、展开运算符+mapGetters辅助函数(...mapGetters)
mapGetters辅助函数仅仅是将 store 中的 getter 映射到局部计算属性
<template>
<div>
<p>{{message}}</p>
<p>{{msg}}</p>
</div>
</template>
<script>
// 导入mapGetters函数
import { mapGetters } from 'vuex';
export default {
data () {
return {};
},
computed: {
...mapGetters(['message', 'msg'])
},
mounted () {
console.log(this.message);
console.log(this.msg);
}
};
</script>
注意:
getters只负责对外提供数据,不负责修改数据,如果想要修改state中的数据,通过提交mutation
getters中的方法,和组件中的过滤器比较类似,因为过滤器和getters都没有修改原数据,都是对原数据做一层包装,然后提供给调用者
getters和computed比较像,如果gettres引用了state中的某个数据,只要state中的这个数据发生变化,就会立即触发getters的重新求值
四、Mutation
1、变更状态
更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。
Vuex 中的 mutation 非常类似于事件:每个 mutation 都有一个字符串的 事件类型 (type) 和 一个 回调函数 (handler)。这个回调函数就是我们实际进行状态更改的地方,并且它会接受 state 作为第一个参数:
// 创建一个Vuex.Store的实例
const store = new Vuex.Store({
// 存储状态
state: {
count: 0
},
// mutation中最多有两个参数,第一个参数为state,要传多个参数,可传递一个对象
mutations: {
// 类型为 increment 的 mutation
increment (state) {
// 变更状态
state.count++;
},
// 类型为 reduce 的 mutation
reduce (state, n) {
// 变更状态
state.count -= n;
},
// 类型为 change 的 mutation,第二个参数为一个对象
change (state, payload) {
// 变更状态
state.count = state.count + payload.a + payload.b;
}
}
});
2、在Vue组件中提交 mutation
以相应的 type 调用 store.commit 方法
<template>
<div class="hello">
<button @click="add">加1</button>
<button @click="reduce">减2</button>
<button @click="change">change</button>
<h1>{{ count }}</h1>
</div>
</template>
<script>
import { mapState } from 'vuex';
export default {
data () {
return {};
},
computed: {
...mapState(['count'])
},
methods: {
add () {
// 以相应的 type 调用 store.commit 方法
this.$store.commit('increment');
},
reduce () {
// 可以向 store.commit 传入额外的参数,即 mutation 的 载荷(payload)
this.$store.commit('reduce', 2);
},
change () {
// 注意:只可以向 store.commit 传入额外的一个参数
// 如果要传多个参数,就传一个对象作为参数,即大多数情况下,载荷应是一个对象
this.$store.commit('change', {a: 4, b: 6});
}
}
};
</script>
3、对象风格提交mutation
提交 mutation 的另一种方式是直接使用包含 type 属性的对象
当使用对象风格的提交方式,整个对象都作为载荷传给 mutation 函数
<template>
<div class="hello">
<button @click="add">加1</button>
<button @click="reduce">减2</button>
<button @click="change">change</button>
<h1>{{ count }}</h1>
</div>
</template>
<script>
import { mapState } from 'vuex';
export default {
data () {
return {};
},
computed: {
...mapState(['count'])
},
methods: {
add () {
// 以相应的 type 调用 store.commit 方法
this.$store.commit({
type: 'increment'
});
},
reduce () {
// 这种没法改用对象风格提交mutation
// 因为第二个参数必须是对象才能使用对象风格提交mutation
this.$store.commit('reduce', 2);
},
change () {
this.$store.commit({
type: 'change',
a: 4,
b: 6
});
}
}
};
</script>
4、展开运算符+mapMutations辅助函数(...mapMutations)
<template>
<div class="hello">
<button @click="increment">加1</button>
<button @click="reduce(2)">减2</button>
<button @click="change({a:4, b:6})">change</button>
<h1>{{ count }}</h1>
</div>
</template>
<script>
// 导入mapState和mapMutations函数
import { mapState, mapMutations } from 'vuex';
export default {
data () {
return {};
},
computed: {
...mapState(['count'])
},
methods: {
// 展开运算符+mapMutations辅助函数
...mapMutations(['increment', 'reduce', 'change'])
}
};
</script>
五、Action
1、异步变更状态
// 创建一个Vuex.Store的实例
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment (state) {
state.count++;
}
},
// 异步变更状态,Action 提交的也是 mutation
// Action 函数接受一个与 store 实例具有相同方法和属性的 context 对象
// Action调用 context.commit 提交一个 mutation
// Action通过 context.state 和 context.getters 获取 state 和 getters。
actions: {
increment (context) {
context.commit('increment');
}
}
});
2、在组件中使用actions
在组件中使用actions和在组建中使用mutations类似
区别就是,mutations使用this.$store.commit('mutation方法名')提交mutation;actions使用this.$store.dispatch('action方法名')分发action
<template>
<div>
<button @click="increment">加1</button>
<h1>{{ count }}</h1>
</div>
</template>
<script>
export default {
data () {
return {};
},
methods: {
increment () {
return this.$store.dispatch('increment');
}
}
};
</script>
3、展开运算符+mapActions 辅助函数(...mapActions )
<template>
<div>
<button @click="increment">加1</button>
<h1>{{ count }}</h1>
</div>
</template>
<script>
// 导入mapActions函数
import { mapActions } from 'vuex';
export default {
data () {
return {};
},
methods: {
// 展开运算符+mapActions辅助函数
...mapActions(['increment'])
}
};
</script>
六、Module
Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块,用于解决复杂应用的状态过多,管理复杂的问题
(1)不带命名空间的模块
默认情况下,模块内部的 action、mutation 和 getter 是注册在全局命名空间的
模块的状态默认注册在全局命名空间,为一个对象,对象中的属性是模块中的状态
模块内部的 mutation 和 getter,接收的第一个参数是模块的局部状态对象。
对于模块内部的 getter,根节点状态作为第三个参数(rootState)暴露出来
在模块的muatations中不能使用根节点状态,因为muatations中是同步更新状态
对于模块内部的 action,局部状态通过 context.state 暴露出来,根节点状态则为 context.rootState
(2)带命名空间的模块
如果希望模块具有更高的封装度和复用性,可以通过添加 namespaced: true 的方式使其成为带命名空间的模块。
当带命名空间的模块被注册后,它所有 getter、action 及 mutation 都会自动根据模块注册的路径调整命名。
若需要在全局命名空间内分发 action 或提交 mutation,将 { root: true } 作为第三参数传给 dispatch 或 commit 即可。
getters中使用全局 state 和 getter,rootState 和 rootGetters 会作为第三和第四参数传入 getter
全局 state会通过 context 对象的属性(context.rootState)传入 action
七、总结
1、state中的数据,不能直接修改,如果想要修改,必须通过mutations
2、如果组件想要直接从state上获取数据:需要this.$store.state.***
3、如果组件想要修改数据,必须使用mutations提供的方法,需要通过this.$store.commit('方法的名称', 唯一的一个参数)
4、如果store中state上的数据,对外提供的时候需要做一层包装,推荐使用getters
5、如果使用getters,则在组建中获取数据,使用this.$store.getters.***