vuex
vuex是专门为vue.js开发的状态管理模式,它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
1.store
每一个Vuex应用的核心就是store(仓库),store基本上就是一个容器,包含着你的应用中大部分的状态(state)。vuex和单纯的全局对象有2点不同:
1.vuex的状态存储是响应式的。当vue组件从store中读取状态时候,若store中的状态发生变化,相应的组件也会相应的得到更新。
2.你不能直接改变store中的状态。改变store中的唯一途径就是显示的提交(commit)mutation
。这样使得我们方便的追踪每一个状态的变化。
创建一个store
<html>
<head>
</head>
<body>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script src="https://unpkg.com/vuex@4.0.0/dist/vuex.global.js"></script>
<div >
</div>
<script>
const { createApp } = Vue
const { createStore } = Vuex
// 创建一个新的 store 实例
const store = createStore({
state () {
return {
count: 0
}
},
mutations: {
increment (state) {
state.count++
}
}
})
const Counter={
template:`<div>{{count}}</div>`,
computed:{
count(){
return store.state.count;
}
}
};
const app = createApp({ /* 根组件 */ });
// 将 store 实例作为插件安装
app.use(store);
store.commit('increment');
console.log(store.state.count);
</script>
</body>
</html>
可以通过store.state来获取状态对象,通过store.commit来触发状态变更:
store.commit("increment");
console.log(store.state.count);
在Vue组件中,可以通过this.$store访问store实例,现在我们可以从组件的方法提交一个变更。
<html>
<head>
</head>
<body>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script src="https://unpkg.com/vuex@4.0.0/dist/vuex.global.js"></script>
<div id="app">
<button @click="itn">+</button>
</div>
<script>
const { createApp } = Vue
const { createStore } = Vuex
// 创建一个新的 store 实例
const store = createStore({
state () {
return {
count: 0
}
},
mutations: {
increment (state) {
state.count++
}
},
})
const app = createApp({ /* 根组件 */
methods:{
itn(){
this.$store.commit('increment')
console.log(this.$store.state.count);
}
}
})
// 将 store 实例作为插件安装
app.use(store);
app.mount("#app");
</script>
</body>
</html>
再次强调,我们通过提交mutation的方式,而非直接改变store.state.count,是因为我们想要更明确的追踪到状态的变化。
由于store的状态是响应式的,在组件中调用store中的状态简单到仅需在计算属性中返回即可,触发变化也仅仅是在组件的methods中提交mutation
2.State
在vue组件中获得Vuex状态
由于Vuex的状态存储是响应式的,从store实例中读取状态最简单的方法:为在计算属性中返回某个状态:
const counter={
template:`<div>{{count}}</div>`,
computed:{
count(){
return store.state.count;
}
}
};
完整代码:
<html>
<head>
</head>
<body>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script src="https://unpkg.com/vuex@4.0.0/dist/vuex.global.js"></script>
<div id="app">
<my-component></my-component>
<button @click="itn">+</button>
</div>
<script>
const { createApp } = Vue
const { createStore } = Vuex
// 创建一个新的 store 实例
const store = createStore({
state () {
return {
count: 0
}
},
mutations: {
increment (state) {
state.count++
}
},
})
const counter={
template:`<div>{{count}}</div>`,
computed:{
count(){
return store.state.count;
}
}
};
const app = createApp({ /* 根组件 */
methods:{
itn(){
this.$store.commit('increment')
console.log(this.$store.state.count);
}
},
components:{
"my-component":counter
}
})
// 将 store 实例作为插件安装
app.use(store);
app.mount("#app");
</script>
</body>
</html>
每当store.state.count变化的时候,都会重新求取计算属性,并且触发更多相关联的dom。
然而,这种模式导致组件依赖全局状态单例。在模块化的构建系统中,在每个需要使用state的组件中频繁的导入,并在测试组件时需要模拟状态。
vuex通过vue的插件系统将store实例从根组件注入到所有的子组件中。且子组件通过this.store访问到,
const Counter = {
template: `<div>{{ count }}</div>`,
computed: {
count () {
return this.$store.state.count
}
}
}
3.Getter
有时我们从store中的state中派生出一些状态。
如果有多个组件需要用到此属性,我们要么复制这个函数,或者抽取到一个共享函数然后在多处导入它。
Vuex允许我们在store中定义getter,getter类似于计算属性值。
在getters中定义了doneTodos函数,该方法接收state参数,filter方法接收的参数为箭头函数,箭头函数的参数todo表示数组中的每一个对象,使用todo.done作为返回值返回,若返回值为true,在filter方法返回的数组中添加todo。
<html>
<head>
</head>
<body>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script src="https://unpkg.com/vuex@4.0.0/dist/vuex.global.js"></script>
<div id="app">
<p>{{this.$store.getters}}</p>
</div>
<script>
const { createApp } = Vue
const { createStore } = Vuex
// 创建一个新的 store 实例
const store=createStore({
state:{
todos:[
{id:1,text:"success",done:true},
{id:2,text:"failure",done:false}
]
},
getters:{
doneTodos(state){
return state.todos.filter(todo=>todo.done);
}
}
})
const app = createApp({ /* 根组件 */
})
// 将 store 实例作为插件安装
app.use(store);
app.mount("#app");
</script>
</body>
</html>
结果表示捕获到了todos数组中done为true的值
利用getters还可以获取doneTodos的数组,以及length值,getter也可以接收其他getter作为第二个参数值
getters:{
doneTodosCount:(state,getters)=>{
return getters.doneTodos.length;
}
}
完整代码:
<html>
<head>
</head>
<body>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script src="https://unpkg.com/vuex@4.0.0/dist/vuex.global.js"></script>
<div id="app">
<p>{{this.$store.getters.doneTodosCount}}</p>
</div>
<script>
const { createApp } = Vue
const { createStore } = Vuex
// 创建一个新的 store 实例
const store=createStore({
state:{
todos:[
{id:1,text:"success",done:true},
{id:2,text:"failure",done:false}
]
},
getters:{
doneTodos(state){
return state.todos.filter(todo=>todo.done);
},
doneTodosCount:(state,getters)=>{
return getters.doneTodos.length;
}
}
})
const app = createApp({ /* 根组件 */
})
// 将 store 实例作为插件安装
app.use(store);
app.mount("#app");
</script>
</body>
</html>
结果:
注意,getter 在通过属性访问时是作为 Vue 的响应式系统的一部分缓存其中的。
4.Mutation
更改Vuex的store的状态的唯一方法为提交mutation。Vuex中的mutation非常类似于事件:每个mutation有一个回调函数,这个回调函数是我们进行状态更改,他会接收state作为第一个参数:
const store = createStore({
state: {
count: 1
},
mutations: {
increment (state) {
// 变更状态
state.count++
}
}
})
不能直接调用一个mutation函数,它更像是事件注册:“当触发一个类型为increment的mutation时,调用此函数。要唤醒一个mutation处理函数,需要调用相应的store.commit方法:
store.commit("increment")
完整代码:
<html>
<head>
</head>
<body>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script src="https://unpkg.com/vuex@4.0.0/dist/vuex.global.js"></script>
<div id="app">
<p>{{this.$store.state.count}}</p>
</div>
<script>
const { createApp } = Vue
const { createStore } = Vuex
// 创建一个新的 store 实例
const store=createStore({
state:{
count:1
},
mutations:{
increment(state){
state.count++;
}
}
})
const app = createApp({ /* 根组件 */
})
// 将 store 实例作为插件安装
app.use(store);
app.mount("#app");
store.commit("increment");
</script>
</body>
</html>
显示结果为2
提交载荷(Payload)
可以向store.commit传入额外的参数,即mutation的载荷(payload):
<html>
<head>
</head>
<body>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script src="https://unpkg.com/vuex@4.0.0/dist/vuex.global.js"></script>
<div id="app">
<p>{{this.$store.state.count}}</p>
</div>
<script>
const { createApp } = Vue
const { createStore } = Vuex
// 创建一个新的 store 实例
const store=createStore({
state:{
count:1
},
mutations:{
increment(state,n){
state.count+=n;
}
}
})
const app = createApp({ /* 根组件 */
})
// 将 store 实例作为插件安装
app.use(store);
app.mount("#app");
store.commit("increment",10);
</script>
</body>
</html>
结果为11
在大多数情况下,载荷应该是一个对象,这样可以包含多个字段并且记录的 mutation 会更易读:
<html>
<head>
</head>
<body>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script src="https://unpkg.com/vuex@4.0.0/dist/vuex.global.js"></script>
<div id="app">
<p>{{this.$store.state.count}}</p>
</div>
<script>
const { createApp } = Vue
const { createStore } = Vuex
// 创建一个新的 store 实例
const store=createStore({
state:{
count:1
},
mutations:{
increment(state,payload){
state.count+=payload.amount;
}
}
})
const app = createApp({ /* 根组件 */
})
// 将 store 实例作为插件安装
app.use(store);
app.mount("#app");
store.commit("increment",{
amount:10
});
</script>
</body>
</html>
对象风格提交方式
提交mutation的另一种方式为直接使用包含type属性的对象,当使用对象风格的提交方式,整个对象都作为载荷传给mutation函数,因此处理函数保持不变:
<html>
<head>
</head>
<body>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script src="https://unpkg.com/vuex@4.0.0/dist/vuex.global.js"></script>
<div id="app">
<p>{{this.$store.state.count}}</p>
</div>
<script>
const { createApp } = Vue
const { createStore } = Vuex
// 创建一个新的 store 实例
const store=createStore({
state:{
count:1
},
mutations:{
increment(state,payload){
state.count+=payload.amount;
}
}
})
const app = createApp({ /* 根组件 */
})
// 将 store 实例作为插件安装
app.use(store);
app.mount("#app");
store.commit({type:"increment",amount:10});
</script>
</body>
</html>
同步函数
mutations是同步函数,组件状态发生变化时,触发mutations中的事件处理方法来更新页面状态的变化,这是一种同步状态。同步方法是同步执行的,主要可以记录当前状态的变化,同步到页面。
5.Action
Action类似于mutation,不同在于:
1.Action提交的是mutation,而不是直接变更状态
2.Action可以包含任意操作
actions用来定义事件处理方法,用于处理state数据,actions是异步执行的,事件处理函数可以接受{commit}对象,完成mutation提交
首先先注册一个简单的action:
const store=createStore({
state:{
count:1
},
mutations:{
increment(state,payload){
state.count+=payload.amount;
}
},
actions:{
increment(context){
context.commit("increment");
}
}
})
Actions接收一个与store实例具有相同方法和属性的context对象,因此可以调用context.commit提交一个mutation,或者通过context.state和context.getters来获取state和getters和dispath。
actions: {
increment ({ commit }) {
commit('increment')
}
}
分发action
Action通过store.dispath方法触发:
store.dispatch("increment");
完整代码:
<html>
<head>
</head>
<body>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script src="https://unpkg.com/vuex@4.0.0/dist/vuex.global.js"></script>
<div id="app">
<button @click="calc">+</button>
<p>{{this.$store.state.count}}</p>
</div>
<script>
const { createApp } = Vue
const { createStore } = Vuex
// 创建一个新的 store 实例
const store=createStore({
state:{
count:1
},
mutations:{
increment(state){
state.count++;
}
},
actions:{
add(context){ //在store实例的actions中定义了add方法,add方法接收参数context参数
context.commit("increment"); //使用commit推送一个名称为increment的mutation,对应mutations中的increment方法
}
}
})
const app = createApp({ /* 根组件 */
methods:{
calc(){
this.$store.dispatch("add"); //通过dispatch来推送一个名称为add的action
}
}
})
// 将 store 实例作为插件安装
app.use(store);
app.mount("#app");
</script>
</body>
</html>
6.module
由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。
Vuex允许我们将store分割为模块(module)。每个模块拥有自己的state、mutation、action、getter
<html>
<head>
</head>
<body>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script src="https://unpkg.com/vuex@4.0.0/dist/vuex.global.js"></script>
<div id="app">
<button @click="calc">+</button>
<p>{{this.$store.state.count}}</p>
</div>
<script>
const { createApp } = Vue
const { createStore } = Vuex
const moduleA={
state:{
nameA:"A"
}
}
const moduleB={
state:{
nameB:"B"
}
}
// 创建一个新的 store 实例
const store=new Vuex.Store({
modules:{
a:moduleA,
b:moduleB
}
})
const app = createApp({ /* 根组件 */
})
// 将 store 实例作为插件安装
app.use(store);
app.mount("#app");
console.log(store.state.a);
console.log(store.state.b);
</script>
</body>
</html>