Pinia极速入门:核心概念与入门指南

Pinia极速入门:核心概念与入门指南

前言:作为 Vue3 的官方推荐状态管理库,Pinia 比 Vuex 更轻量、更简单,且完美支持 TypeScript。本文将带你通过"项目实战"的方式,快速掌握 Pinia 的核心用法,并避开新手最容易踩的坑。

1. 为什么选择 Pinia?

简单来说,Pinia 去掉了 Vuex 中复杂的 Mutation,只保留了 StateGettersActions。它用起来就像是一个全局的组件,非常符合 Vue3 组合式 API 的思维。

2. 快速开始

首先,在 main.js 中注册 Pinia。这是所有故事的开始。

文件路径 : src/main.js

javascript 复制代码
import { createApp } from "vue";
import { createPinia } from "pinia"; // 导入 createPinia
import App from "./App.vue";

const app = createApp(App);

const pinia = createPinia(); // 创建实例
app.use(pinia); // 注册插件

app.mount("#app");

3. 核心概念三剑客

3.1 定义 Store (Defining a Store)

我们以 counter.js 为例。虽然 Pinia 支持"组合式写法 (Setup Store)",但为了方便上手,我们先看从 Vuex 过度更自然的"选项式写法 (Option Store)"。

文件路径 : src/stores/counter.js

选项式

javascript 复制代码
import { defineStore } from "pinia";
import useUser from "./user"; // 引入其他 Store

export const useCounter = defineStore("counter", {
  // 1. State: 相当于组件的 data,用于存储数据
  state: () => ({
    count: 99,
    friends: [
      { id: 111, name: "why" },
      { id: 112, name: "kobe" },
    ],
  }),

  // 2. Getters: 相当于组件的 computed,用于计算数据
  getters: {
    doubleCount(state) {
      return state.count * 2;
    },
    // 支持使用 this 访问其他 getter
    doubleCountAddOne() {
      return this.doubleCount + 1;
    },
    // 跨 Store 访问
    showMessage(state) {
      const userStore = useUser();
      return `name:${userStore.name}-count:${state.count}`;
    }
  },

  // 3. Actions: 相当于组件的 methods,用于修改数据(支持异步!)
  actions: {
    increment(num) {
      // 也可以直接通过 this.count++ 修改,但封装 action 更规范
      this.count += num;
    },
  },
});

export default useCounter;

组合式

javascript 复制代码
import { defineStore } from "pinia";
import { ref, computed } from "vue"; // 引入 Vue 的核心响应式 API
import useUser from "./user"; // 引入其他 Store

// 第二个参数传入一个函数,而不是对象
export const useCounter = defineStore("counter", () => {
  // --- 1. State (对应 ref/reactive) ---
  const count = ref(99);
  const friends = ref([
    { id: 111, name: "why" },
    { id: 112, name: "kobe" },
  ]);

  // --- 引入其他 Store (在函数体内直接调用即可) ---
  const userStore = useUser();

  // --- 2. Getters (对应 computed) ---
  const doubleCount = computed(() => {
    return count.value * 2; // 注意:在 script 中访问 ref 需要 .value
  });

  // 访问内部其他 computed
  const doubleCountAddOne = computed(() => {
    return doubleCount.value + 1;
  });

  // 跨 Store 访问
  const showMessage = computed(() => {
    // 直接使用上面定义的 userStore 变量
    return `name:${userStore.name}-count:${count.value}`;
  });

  // --- 3. Actions (对应普通 function) ---
  function increment(num) {
    count.value += num;
  }

  // --- 4. 必须 return ---
  // 将需要暴露给组件使用的状态和方法返回
  return {
    count,
    friends,
    doubleCount,
    doubleCountAddOne,
    showMessage,
    increment,
  };
});

export default useCounter;

核心映射关系表

为了帮助你更好地理解这种转换,这里有一个简单的对照表:

概念 选项式 (Option Store) 组合式 (Setup Store) 关键点
定义方式 defineStore('id', { ... }) defineStore('id', () => { ... }) 参数变为函数
State state: () => ({ count: 0 }) const count = ref(0) 使用 refreactive
Getters getters: { double() {...} } const double = computed(() => ...) 使用 computed
Actions actions: { inc() {...} } function inc() { ... } 使用普通函数
跨 Store 在 getter/action 内部调用 在函数体顶部直接调用 useX() 作用域更清晰
访问数据 this.count count.value 最容易出错的地方!

为什么推荐用组合式写法?

  1. 更强的逻辑复用 :你可以在 Store 内部使用 Vue 的 watchonMounted 甚至提取出来的 Hooks (Composables),这在选项式写法中很难做到。
  2. 代码组织更自由 :你不再受限于 stategettersactions 三个区块。你可以把相关的 state 和 function 写在一起,这对大型 Store 非常友好。
  3. 类型推导更好:对于 TypeScript 用户来说,Setup Store 的类型推导通常比 Option Store 更顺畅。

温馨提示:

换成组合式写法后,在组件中解构数据时有一个经典的"坑"(丢失响应性)。


4. 组件中使用与"解构陷阱"

这是新手最容易踩的坑:直接解构 Store 会导致响应式丢失!

场景 :你想在组件里直接用 count,而不是 counterStore.count

javascript 复制代码
<script setup>
import useCounter from "@/stores/counter";
import { storeToRefs } from "pinia"; // 关键工具!

const counterStore = useCounter();

// 错误做法:直接解构,count 变成普通变量,不再响应式
// const { count } = counterStore; 

// 正确做法:使用 storeToRefs 包裹
const { count } = storeToRefs(counterStore);

// 注意:Actions (方法) 可以直接解构,不需要 storeToRefs
const { increment } = counterStore; 
</script>

<template>
  <div>
    <!-- 使用 store 实例访问 -->
    <h2>Count (Store): {{ counterStore.count }}</h2>
    
    <!-- 使用解构后的响应式数据 -->
    <h2>Count (Ref): {{ count }}</h2>
    
    <button @click="increment(1)">+1</button>
  </div>
</template>

原理小贴士storeToRefs 会把 State 和 Getters 里的属性都变成 Ref 对象,而保留 Actions 这样解构出来依然保持响应式引用。


5. 常用简便方法

除了基础用法,Pinia 还提供了一些极其好用的 API,能极大提升开发效率。

5.1 $patch:同时修改多个状态

如果你需要一次性修改多个数据,用 $patch 性能更好,语义更清晰。

javascript 复制代码
const handleClick = () => {
  // 方式一:对象式
  counterStore.$patch({
    count: 100,
    friends: [...counterStore.friends, { id: 114, name: 'curry' }]
  });
  
  // 方式二:函数式 (适合复杂逻辑)
  counterStore.$patch((state) => {
    state.count++;
    state.friends.push({ id: 115, name: 'durant' });
  });
};

5.2 $reset:重置状态

想把状态恢复到初始值?不再需要手动写 reset 函数了。
(注意:仅限 Option Store 写法支持,Setup Store 需要自己实现)

javascript 复制代码
counterStore.$reset(); // count 回到 99, friends 回到初始数组

6. 总结

  1. 定义 :使用 defineStore,结构类似 Vue 组件 (state, getters, actions)。
  2. 使用 :组件内 const store = useStore()
  3. 避坑解构 State 必须用 storeToRefs,否则失去响应式。
  4. 修改 :可以直接修改 store.count++,也可以用 Actions 或 $patch

希望这篇笔记能帮你快速上手 Pinia!如果有任何疑问,欢迎在评论区留言交流。

相关推荐
余瑜鱼鱼鱼2 小时前
Thread类中run和start的区别
java·开发语言·前端
n 55!w !1082 小时前
js练习作业
开发语言·javascript·ecmascript
计算机程序设计小李同学2 小时前
基于位置服务的二手图书回收平台
java·前端·vue.js·spring boot·后端
Whisper_Sy2 小时前
Flutter for OpenHarmony移动数据使用监管助手App实战 - 月报告实现
android·开发语言·javascript·网络·flutter·ecmascript
灰灰勇闯IT2 小时前
【Flutter for OpenHarmonyDart 入门日记】第5篇:字典类型 Map 与动态类型 dynamic 全解析
开发语言·javascript·ecmascript
雨季6662 小时前
Flutter for OpenHarmony 入门实践:从 Scaffold 到 Container 的三段式布局构建
开发语言·javascript·flutter
Dreamy smile2 小时前
JavaScript 继承与 this 指向操作详解
开发语言·javascript·原型模式
We་ct2 小时前
LeetCode 14. 最长公共前缀:两种解法+优化思路全解析
前端·算法·leetcode·typescript
遗憾随她而去.2 小时前
前端检查内存泄露
前端