Pinia/Vuex 4的状态管理集成:State的响应性Proxy封装与Mutation/Action的调度

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 使用 refcomputed 来定义 State 和 Getter。

3.1 refcomputed 函数

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.valuemessage.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 函数接受一个包含 statecommitdispatchgettersrootStatecontext 对象作为第一个参数。

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精英技术系列讲座,到智猿学院

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注