vue3 - pinia状态管理库

概念

Pinia 是 Vue 官方推荐的状态管理库,是 Vuex 的继任者(Vuex 作者同一人开发),专门为 Vue 3 设计,完全支持 Composition API 和 TypeScript。它简化了状态管理的流程,提供了更简洁的 API 和更好的开发体验。

核心特点

  1. 简洁的 API
    去掉了 Vuex 中的 mutation(突变),直接通过 actions 或直接修改状态(对 state 直接赋值),减少模板代码。
  2. 完全支持 TypeScript
    类型推断更友好,无需手动声明类型即可获得完整的类型提示,开发时能及时发现类型错误。
  3. 与 Composition API 无缝集成
    可以在 setup 中轻松使用,支持直接解构状态而不失去响应性。
  4. 模块化设计
    每个 store 都是一个独立的模块,无需像 Vuex 那样嵌套 modules,结构更清晰。
  5. 轻量体积
    核心代码仅约 1KB,性能优于 Vuex。

Pinia 核心概念

  1. Store(仓库):存储应用状态的容器,每个 Store 是一个独立的模块
  2. State:存储的核心数据
  3. Getters:基于 State 的计算属性
  4. Actions:用于修改 State 的方法(支持异步操作)

安装pinia

npm i pinia

使用 pinia

src\main.ts

import { createApp } from "vue";
import App from "./App.vue";
// 第一步:引入pinia
import { createPinia } from "pinia";

const app = createApp(App);
// 第二步:创建pinia
const pinia = createPinia();
// 第三步:安装pinia
app.use(pinia);
app.mount("#app");

安装成功

image

存储和读取数据

创建store

src\store\sum.ts

import { defineStore } from "pinia";

// 定义并导出一个 store
export const useSumStore = defineStore("sum", {
  // 状态
  state: () => ({
    sum: 4,
  }),
});

在组件中的使用

src\components\SumComponent.vue

<template>
    <div class="sum-container">
        <h3>当前求和为:{{ sum }}</h3>
        <select v-model="number">
            <option value="1">1</option>
            <option value="2">2</option>
            <option value="3">3</option>
            <option value="4">4</option>
        </select>
        <button @click="add">加</button>
        <button @click="subtract">减</button>
    </div>
</template>

<script setup>
import { ref } from 'vue';
import { useSumStore } from '@/store/sum';
// const sum = ref(4);
const sum = useSumStore().sum;
const number = ref('1');

const add = () => {
    // sum.value += parseInt(number.value);
};

const subtract = () => {
    // sum.value -= parseInt(number.value);
};
</script>

修改数据的三种方式

第1种:直接改值

 useSumStore().sum += parseInt(number.value);

第2种:$patch

调用 $patch 方法。它允许你用一个 state 的补丁对象在同一时间更改多个属性:

store.$patch({
  count: store.count + 1,
  age: 120,
  name: 'DIO',
})

第3种方式actions

src\store\sum.ts

import { defineStore } from "pinia";

// 定义并导出一个 store
export const useSumStore = defineStore("sum", {
  // 状态
  state: () => ({
    sum: 4,
  }),
  actions: {
    increment(value: number) {
      this.sum += value;
    },
  },
});

src\components\SumComponent.vue

<template>
    <div class="sum-container">
        <h3>当前求和为:{{ useSumStore().sum }}</h3>
        <select v-model="number">
            <option value="1">1</option>
            <option value="2">2</option>
            <option value="3">3</option>
            <option value="4">4</option>
        </select>
        <button @click="add">加</button>
        <button @click="subtract">减</button>
    </div>
</template>

<script setup>
import { ref } from 'vue';
import { useSumStore } from '@/store/sum';
// const sum = ref(4);
const number = ref('1');

const add = () => {
    //第1种写法:直接改值
    // useSumStore().sum += parseInt(number.value);

    //第2种写法:$patch
    // useSumStore().$patch({
    //     sum: useSumStore().sum + parseInt(number.value),
    // });

    //第3种写法:actions 方法
    useSumStore().increment(parseInt(number.value));
};

const subtract = () => {
    // sum.value -= parseInt(number.value);
    useSumStore().sum -= parseInt(number.value);
};
</script>

storeToRefs 解构

在 Pinia 中,storeToRefs 是一个非常实用的工具函数,用于将 Pinia 仓库(store)中的状态(state)和 getter 转换为响应式的 ref 对象,方便在组件中使用。

const sumStore = useSumStore();
const { sum } = storeToRefs(sumStore); // 获取store中的值

console.log("toRefs", toRefs(sumStore))
console.log("storeToRefs", storeToRefs(sumStore))

image

所以

<template>
    <div class="sum-container">
        <h3>当前求和为:{{ sum }}</h3>
        <select v-model="number">
            <option value="1">1</option>
            <option value="2">2</option>
            <option value="3">3</option>
            <option value="4">4</option>
        </select>
        <button @click="add">加</button>
        <button @click="subtract">减</button>
    </div>
</template>

<script setup>
import { ref } from 'vue';
import { useSumStore } from '@/store/sum';
import { storeToRefs } from 'pinia';
// const sum = ref(4);
const number = ref('1');
const sumStore = useSumStore();
const { sum } = storeToRefs(sumStore); // 获取store中的值

const add = () => {
    sumStore.increment(parseInt(number.value));
};

const subtract = () => {
    // sum.value -= parseInt(number.value);
    sumStore.sum -= parseInt(number.value);
};
</script>

getters

基本使用

Getter 完全等同于 store 的 state 的计算值。可以通过 defineStore() 中的 getters 属性来定义它们。推荐使用箭头函数,并且它将接收 state 作为第一个参数:

export const useSumStore = defineStore("sum", {
  // 状态
  state: () => ({
    sum: 4,
  }),
  actions: {
    increment(value: number) {
      this.sum += value;
    },
  },
  getters: {
    bigSumNumber: (state) => {
      return state.sum * 10;
    }
  },
});

访问其他 getter

大多数时候,getter 仅依赖 state。不过,有时它们也可能会使用其他 getter。因此,即使在使用常规函数定义 getter 时,我们也可以通过 this 访问到整个 store 实例但(在 TypeScript 中)必须定义返回类型。这是为了避免 TypeScript 的已知缺陷,不过这不影响用箭头函数定义的 getter,也不会影响不使用 this 的 getter

import { defineStore } from "pinia";

// 定义并导出一个 store
export const useSumStore = defineStore("sum", {
  // 状态
  state: () => ({
    sum: 4,
  }),
  actions: {
    increment(value: number) {
      this.sum += value;
    },
  },
  getters: {
    bigSumNumber: (state) => {
      return state.sum * 10;
    },
    // 返回类型**必须**明确设置
    newSumNumber(): number {
      // 整个 store 的 自动补全和类型标注 ✨
      return this.sum * 10;
    },
  },
});

向 getter 传递参数

Getter 只是幕后的计算属性,所以不可以向它们传递任何参数。不过,你可以从 getter 返回一个函数,该函数可以接受任意参数:

js

export const useUserListStore = defineStore('userList', {
  getters: {
    getUserById: (state) => {
      return (userId) => state.users.find((user) => user.id === userId)
    },
  },
})

并在组件中使用:

<script setup>
import { useUserListStore } from './store'
const userList = useUserListStore()
const { getUserById } = storeToRefs(userList)
// 请注意,你需要使用 `getUserById.value` 来访问
// <script setup> 中的函数
</script>

<template>
  <p>User 2: {{ getUserById(2) }}</p>
</template>

请注意,当你这样做时,getter 将不再被缓存。它们只是一个被你调用的函数。不过,你可以在 getter 本身中缓存一些结果,虽然这种做法并不常见,但有证明表明它的性能会更好:

export const useUserListStore = defineStore('userList', {
  getters: {
    getActiveUserById(state) {
      const activeUsers = state.users.filter((user) => user.active)
      return (userId) => activeUsers.find((user) => user.id === userId)
    },
  },
})

订阅 state:subscribe 方法

类似于 Vuex 的 subscribe 方法,你可以通过 store 的 $subscribe() 方法侦听 state 及其变化。比起普通的 watch(),使用 $subscribe() 的好处是 subscriptionspatch 后只触发一次 (例如,当使用上面的函数版本时)。

cartStore.$subscribe((mutation, state) => {
  // import { MutationType } from 'pinia'
  mutation.type // 'direct' | 'patch object' | 'patch function'
  // 和 cartStore.$id 一样
  mutation.storeId // 'cart'
  // 只有 mutation.type === 'patch object'的情况下才可用
  mutation.payload // 传递给 cartStore.$patch() 的补丁对象。

  // 每当状态发生变化时,将整个 state 持久化到本地存储。
  localStorage.setItem('cart', JSON.stringify(state))
})

刷新时机

在底层实现上,$subscribe() 使用了 Vue 的 watch() 函数。你可以传入与 watch() 相同的选项。当你想要在 每次 state 变化后立即触发订阅时很有用:

cartStore.$subscribe((mutation, state) => {
  // 每当状态发生变化时,将整个 state 持久化到本地存储
  localStorage.setItem('cart', JSON.stringify(state))
}, { flush: 'sync' })

取消订阅

默认情况下,state subscription 会被绑定到添加它们的组件上 (如果 store 在组件的 setup() 里面)。这意味着,当该组件被卸载时,它们将被自动删除。如果你想在组件卸载后依旧保留它们,请将 { detached: true } 作为第二个参数,以将 state subscription 从当前组件中分离

<script setup>
const someStore = useSomeStore()
// 此订阅器即便在组件卸载之后仍会被保留
someStore.$subscribe(callback, { detached: true })
</script>

TIP

你可以在 pinia 实例上使用 watch() 函数侦听整个 state。

watch(
  pinia.state,
  (state) => {
    // 每当状态发生变化时,将整个 state 持久化到本地存储。
    localStorage.setItem('piniaState', JSON.stringify(state))
  },
  { deep: true }
)

store 的组合式写法

选项式写法:

import { defineStore } from "pinia";
import axios from "axios";

// 定义并导出一个 store
export const useTalksStore = defineStore("talks", {
  // 状态
  state: () => ({
    talks: [
      {
        id: 1,
        title: "你今天有点怪,哪里怪?怪好看的!",
      },
      {
        id: 2,
        title: "草莓、蓝莓、蔓越莓,你想我了没?",
      },
      {
        id: 3,
        title: "心里给你留了一块地,我的死心塌地",
      },
    ],
  }),
  actions: {
    async getTalk() {
      let loading = true;
      let errorMsg = "";
      try {
        const { data } = await axios.get(`/src/data/lovetasks.json`);
        console.log(data);
        const maxId =
          this.talks.length > 0
            ? Math.max(...this.talks.map((talk) => talk.id)) + 1
            : 0;
        for (const key in data) {
          let talk = data[key].title;
          //判断tasks的title是否包含当前task的title
          if (!this.talks.some((item) => item.title === talk)) {
            this.talks.unshift({ id: maxId, title: talk });
            return;
          }
        }
      } catch (err) {
        errorMsg = "网络错误,请稍后重试";
        console.error(err);
      } finally {
        loading = false;
      }
    },
  },
});

改为组合式写法,不要忘记 return

import { defineStore } from "pinia";
import axios from "axios";
import { reactive } from "vue";

//组合式写法
export const useTalksStore = defineStore("talks", () => {
  //state
  const talks = reactive([
    {
      id: 1,
      title: "你今天有点怪,哪里怪?怪好看的!",
    },
    {
      id: 2,
      title: "草莓、蓝莓、蔓越莓,你想我了没?",
    },
    {
      id: 3,
      title: "心里给你留了一块地,我的死心塌地",
    },
  ]);

  //actions
  async function getTalk() {
    let loading = true;
    let errorMsg = "";
    try {
      const { data } = await axios.get(`/src/data/lovetasks.json`);
      console.log(data);
      const maxId =
        talks.length > 0 ? Math.max(...talks.map((talk) => talk.id)) + 1 : 0;
      for (const key in data) {
        let talk = data[key].title;
        //判断tasks的title是否包含当前task的title
        if (!talks.some((item) => item.title === talk)) {
          talks.unshift({ id: maxId, title: talk });
          return;
        }
      }
    } catch (err) {
      errorMsg = "网络错误,请稍后重试";
      console.error(err);
    } finally {
      loading = false;
    }
  }

  return { talks, getTalk };
});
posted @ 2025-09-12 11:09  【唐】三三  阅读(27)  评论(0)    收藏  举报