Pinia/Vuex 4 的状态管理集成:State 的响应式 Proxy 封装与 Mutation/Action 的调度
大家好,今天我们深入探讨 Pinia 和 Vuex 4 这两个流行的 Vue.js 状态管理库的核心机制,重点关注它们如何利用 Proxy 实现 State 的响应式封装,以及如何调度 Mutation 和 Action 来修改 State。我们将通过代码示例和逻辑分析,帮助大家理解这些概念,并能在实际项目中更好地运用它们。
1. 状态管理库的核心概念
在深入具体实现之前,我们先回顾一下状态管理库的一些核心概念:
- State (状态):应用程序的数据源,存储应用的所有数据。
- Getter (获取器):从 State 派生出的计算属性,用于获取和格式化 State 中的数据。
- Mutation (变更):同步修改 State 的唯一方式。
- Action (动作):提交 Mutation 的方式,可以包含异步操作。
- Store (仓库):包含 State、Getter、Mutation 和 Action 的集合。
2. Vuex 4 的响应式 State 封装
Vuex 4 依赖于 Vue 3 的响应式系统。在 Vue 3 中,reactive 函数用于创建对象的响应式代理。Vuex 4 的 State 就是通过 reactive 函数进行封装的。
2.1 reactive 函数
reactive 函数接受一个普通 JavaScript 对象作为参数,并返回一个 Proxy 对象。当访问或修改 Proxy 对象的属性时,会触发相应的 get 和 set 拦截器。这些拦截器负责通知 Vue 的依赖追踪系统,从而实现响应式更新。
import { reactive } from 'vue';
const state = reactive({
count: 0,
message: 'Hello Vuex'
});
console.log(state.count); // 访问属性,触发 get 拦截器
state.count++; // 修改属性,触发 set 拦截器
console.log(state.count);
2.2 Vuex 4 中的 State 封装
在 Vuex 4 中,State 被封装在 store._state 属性中。这个 _state 属性是通过 reactive 函数创建的 Proxy 对象。
import { createStore } from 'vuex';
const store = createStore({
state() {
return {
count: 0,
message: 'Hello Vuex'
};
},
mutations: {
increment(state) {
state.count++;
}
}
});
console.log(store.state.count); // 访问 store.state.count,触发 get 拦截器
store.commit('increment'); // 提交 mutation,修改 store.state.count,触发 set 拦截器
console.log(store.state.count);
2.3 Vuex 4 的响应式原理
当组件访问 store.state.count 时,Vue 的依赖追踪系统会将该组件标记为依赖 store.state.count。当 store.state.count 的值发生变化时,Vue 会自动更新依赖于该值的组件。这就是 Vuex 4 实现响应式的基本原理。
3. Pinia 的响应式 State 封装
Pinia 也利用了 Vue 3 的响应式系统,但它并没有像 Vuex 4 那样使用 reactive 函数直接封装整个 State 对象。Pinia 使用 ref 和 computed 来定义 State 和 Getter。
3.1 ref 和 computed 函数
ref 函数用于创建响应式的引用。ref 函数接受一个初始值作为参数,并返回一个包含 value 属性的对象。访问或修改 value 属性会触发相应的 get 和 set 拦截器。
computed 函数用于创建基于其他响应式值的计算属性。computed 函数接受一个 getter 函数作为参数,并返回一个包含 value 属性的对象。value 属性的值是 getter 函数的返回值。当 getter 函数依赖的响应式值发生变化时,value 属性的值会自动更新。
import { ref, computed } from 'vue';
const count = ref(0);
const message = ref('Hello Pinia');
const doubledCount = computed(() => count.value * 2);
console.log(count.value); // 访问 count.value,触发 get 拦截器
count.value++; // 修改 count.value,触发 set 拦截器
console.log(count.value);
console.log(doubledCount.value); // 访问 doubledCount.value,触发 get 拦截器
3.2 Pinia 中的 State 封装
在 Pinia 中,State 通常使用 ref 函数定义。
import { defineStore } from 'pinia';
import { ref } from 'vue';
export const useCounterStore = defineStore('counter', () => {
const count = ref(0);
const message = ref('Hello Pinia');
function increment() {
count.value++;
}
return { count, message, increment };
});
//使用
import { useCounterStore } from './stores/counter';
import { storeToRefs } from 'pinia';
import { computed } from 'vue';
export default {
setup() {
const counterStore = useCounterStore();
const { count, message } = storeToRefs(counterStore); // 使用 storeToRefs 解构响应式变量
const doubledCount = computed(() => count.value * 2);
return {
count,
message,
doubledCount,
increment: counterStore.increment
};
},
template: `
<p>Count: {{ count }}</p>
<p>Doubled Count: {{ doubledCount }}</p>
<p>Message: {{ message }}</p>
<button @click="increment">Increment</button>
`
};
3.3 Pinia 的响应式原理
Pinia 的响应式原理与 Vue 3 的响应式原理相同。当组件访问 count.value 或 message.value 时,Vue 的依赖追踪系统会将该组件标记为依赖这些值。当这些值发生变化时,Vue 会自动更新依赖于这些值的组件。Pinia 通过 storeToRefs API 使得 store 中的 ref 变为组件中直接可以使用的响应式变量,避免了在模板中始终使用 .value 的写法。
4. Mutation 和 Action 的调度
Mutation 和 Action 是修改 State 的两种方式。Mutation 必须是同步的,而 Action 可以包含异步操作。
4.1 Vuex 4 的 Mutation 和 Action 调度
在 Vuex 4 中,Mutation 通过 commit 方法调度,Action 通过 dispatch 方法调度。
import { createStore } from 'vuex';
const store = createStore({
state() {
return {
count: 0
};
},
mutations: {
increment(state) {
state.count++;
}
},
actions: {
incrementAsync(context) {
setTimeout(() => {
context.commit('increment');
}, 1000);
}
}
});
store.commit('increment'); // 同步提交 mutation
store.dispatch('incrementAsync'); // 异步提交 action
4.1.1 commit 方法
commit 方法接受一个 Mutation 类型作为参数,并调用相应的 Mutation 函数。Mutation 函数接受 State 作为第一个参数。
store.commit('increment'); // 提交 'increment' mutation
4.1.2 dispatch 方法
dispatch 方法接受一个 Action 类型作为参数,并调用相应的 Action 函数。Action 函数接受一个包含 state、commit、dispatch、getters 和 rootState 的 context 对象作为第一个参数。
store.dispatch('incrementAsync'); // 提交 'incrementAsync' action
4.2 Pinia 的 Action 调度
在 Pinia 中,Action 就是普通的函数,可以直接调用。Pinia 没有像 Vuex 4 那样区分 Mutation 和 Action。所有的状态修改都在 Action 中进行。
import { defineStore } from 'pinia';
import { ref } from 'vue';
export const useCounterStore = defineStore('counter', () => {
const count = ref(0);
function increment() {
count.value++;
}
async function incrementAsync() {
await new Promise((resolve) => setTimeout(resolve, 1000));
count.value++;
}
return { count, increment, incrementAsync };
});
//使用
import { useCounterStore } from './stores/counter';
export default {
setup() {
const counterStore = useCounterStore();
return {
count: counterStore.count,
increment: counterStore.increment,
incrementAsync: counterStore.incrementAsync
};
},
template: `
<p>Count: {{ count }}</p>
<button @click="increment">Increment</button>
<button @click="incrementAsync">Increment Async</button>
`
};
4.2.1 直接调用 Action
在 Pinia 中,Action 就是普通的函数,可以直接调用。
const counterStore = useCounterStore();
counterStore.increment(); // 直接调用 increment action
counterStore.incrementAsync(); // 直接调用 incrementAsync action
5. Vuex 4 和 Pinia 的差异比较
| 特性 | Vuex 4 | Pinia |
|---|---|---|
| State 封装 | reactive 函数封装整个 State 对象 |
ref 函数封装 State 中的每个属性 |
| Mutation | 必须是同步的,通过 commit 方法调度 |
没有 Mutation 的概念,所有状态修改都在 Action 中进行 |
| Action | 可以包含异步操作,通过 dispatch 方法调度 |
就是普通的函数,可以直接调用 |
| 模块化 | 通过 modules 选项实现模块化 |
通过多个 Store 实现模块化 |
| TypeScript 支持 | 需要额外的类型定义文件才能获得更好的类型支持 | 原生支持 TypeScript,类型推断更强大 |
| 代码体积 | 相对较大 | 相对较小 |
6. 代码示例:一个简单的计数器
下面是一个使用 Vuex 4 和 Pinia 实现的简单计数器示例。
6.1 Vuex 4 示例
<template>
<div>
<p>Count: {{ count }}</p>
<button @click="increment">Increment</button>
<button @click="incrementAsync">Increment Async</button>
</div>
</template>
<script>
import { mapState, mapActions } from 'vuex';
export default {
computed: {
...mapState(['count'])
},
methods: {
...mapActions(['increment', 'incrementAsync'])
}
};
</script>
// store.js
import { createStore } from 'vuex';
export default createStore({
state() {
return {
count: 0
};
},
mutations: {
increment(state) {
state.count++;
}
},
actions: {
increment(context) {
context.commit('increment');
},
incrementAsync(context) {
setTimeout(() => {
context.commit('increment');
}, 1000);
}
}
});
6.2 Pinia 示例
<template>
<div>
<p>Count: {{ count }}</p>
<button @click="increment">Increment</button>
<button @click="incrementAsync">Increment Async</button>
</div>
</template>
<script>
import { useCounterStore } from './stores/counter';
import { storeToRefs } from 'pinia';
export default {
setup() {
const counterStore = useCounterStore();
const { count } = storeToRefs(counterStore); // 解构为响应式变量
return {
count,
increment: counterStore.increment,
incrementAsync: counterStore.incrementAsync
};
}
};
</script>
// stores/counter.js
import { defineStore } from 'pinia';
import { ref } from 'vue';
export const useCounterStore = defineStore('counter', () => {
const count = ref(0);
function increment() {
count.value++;
}
async function incrementAsync() {
await new Promise((resolve) => setTimeout(resolve, 1000));
count.value++;
}
return { count, increment, incrementAsync };
});
7. 选择合适的库
Vuex 4 和 Pinia 都是优秀的状态管理库,选择哪个取决于项目的具体需求。
- Vuex 4:适合大型、复杂项目,需要更严格的状态管理规范。
- Pinia:适合中小型项目,或者需要更灵活、更简洁的状态管理方案。
8. 总结:响应式状态管理和状态变更的调度
我们深入探讨了 Pinia 和 Vuex 4 的状态管理机制,核心在于利用 Vue 3 的响应式系统封装 State,并提供不同的方式来调度状态变更。 Vuex 4 区分 Mutation 和 Action,提供更严格的状态管理规范,而 Pinia 则更加简洁灵活,所有状态修改都在 Action 中进行。选择哪个库取决于项目的具体需求。
希望今天的分享能够帮助大家更好地理解 Pinia 和 Vuex 4,并在实际项目中灵活运用它们。谢谢大家!
更多IT精英技术系列讲座,到智猿学院