Store 是 Vue3 应用的 集中式状态容器,用来管理全局共享的数据(比如用户信息、计数、购物车)。
独立:每个 Store 都是独立模块,互不干扰
三要素:
State:存储数据(响应式)
Getters:计算属性(基于 State 派生数据,缓存结果)
Actions:修改 State 的方法(支持同步 / 异步)
defineStore:Pinia 提供的唯一创建 Store 的方法
创建状态管理容器:src/store/counter.ts
更直观,类似 Vuex
import { defineStore } from 'pinia';
// 1. defineStore(唯一ID, 配置对象)
// 唯一ID:'counter' → 开发者工具中识别 Store 的标识
export const useCounterStore = defineStore('counter', {
// 2. State:必须是函数!返回响应式数据(避免多实例数据污染)
state: () => ({
count: 0 // 初始状态:计数
}),
// 3. Getters:计算属性,基于 state 派生数据(自带缓存)
getters: {
doubleCount: (state) => state.count * 2
},
// 4. Actions:修改 state 的方法(可写业务逻辑)
actions: {
increment() {
// this 指向当前 store 实例
this.count++;
}
}
});Vue3 推荐的方式;新手建议先用上面的选项式,代码看起来更直观
import { defineStore } from 'pinia';
// 导入 Vue 响应式 API
import { computed, ref } from 'vue';
export const useCounterStore = defineStore('counter', () => {
// 1. 用 ref 定义 State(响应式数据)
const count = ref(0);
// 2. 用 computed 定义 Getters
const doubleCount = computed(() => count.value * 2);
// 3. 用普通函数定义 Actions
const increment = () => {
count.value++;
};
// 必须返回!外部组件才能访问这些数据/方法
return { count, doubleCount, increment };
});两种方式完全等价,组件中使用方式一致。
创建路由文件,用于切换不同功能页面,文件:src/router/index.ts
import { createRouter, createWebHistory, type RouteRecordRaw } from 'vue-router';
// 路由规则数组
const routes: Array<RouteRecordRaw> = [
// 首页
{
path: '/',
component: () => import('@/views/home/home.vue'),
name: 'home'
},
// 基础使用 Store
{
path: '/store/use',
component: () => import('@/views/store/use.vue'),
name: 'use-store'
},
// Store 解构
{
path: '/store/deconstruction',
component: () => import('@/views/store/deconstruction.vue'),
name: 'deconstruction'
},
// 监听 State
{
path: '/store/listening',
component: () => import('@/views/store/listening.vue'),
name: 'listening'
},
// 修改 State
{
path: '/store/update',
component: () => import('@/views/store/update.vue'),
name: 'update'
}
];
// 创建路由实例
const router = createRouter({
history: createWebHistory(), // history 模式
routes
});
export default router;解释:
懒加载组件:() => import('...') → 优化页面性能
路由对应我们后续要创建的 4 个功能页面
创建公共布局组件,包含顶部导航菜单,文件:src/components/AppLayout.vue
<template>
<div class="app-layout">
<!-- Element Plus 导航菜单 -->
<el-menu :default-active="activeIndex" mode="horizontal" @select="handleSelect">
<el-menu-item index="/">首页</el-menu-item>
<!-- 子菜单:状态管理 -->
<el-sub-menu index="/store">
<template #title>状态管理</template>
<el-menu-item index="/store/use">使用 Store</el-menu-item>
<el-menu-item index="/store/deconstruction">解构 Store</el-menu-item>
<el-menu-item index="/store/listening">监听 State</el-menu-item>
<el-menu-item index="/store/update">修改 State</el-menu-item>
</el-sub-menu>
</el-menu>
<!-- 页面内容容器:路由匹配的组件渲染在这里 -->
<div class="content">
<RouterView />
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { RouterView, useRouter } from 'vue-router'
const router = useRouter()
// 默认激活的菜单
const activeIndex = ref('/store')
// 菜单点击事件:跳转到对应路由
const handleSelect = (key: string) => {
if (key !== '/store') {
router.push(key)
}
}
</script>
<style scoped>
.app-layout {
width: 100%;
min-height: 100vh;
}
.el-menu-demo {
margin-bottom: 20px;
}
.content {
padding: 20px;
}
</style>解释:
RouterView:路由组件出口,匹配的页面会渲染在这里
菜单 index 对应路由的 path,实现点击跳转
使用我们创建的布局组件,文件:src/App.vue
<script setup lang="ts">
import AppLayout from './components/AppLayout.vue'
</script>
<template>
<!-- 全局布局 -->
<AppLayout />
</template>解释:整个应用的根组件,只引入布局,实现统一页面结构。
文件:src/views/home/home.vue
<template>
<div class="home-page">
<h1>欢迎使用 Pinia 状态管理示例</h1>
<p>请从上方菜单选择要查看的功能</p>
</div>
</template>
<style scoped>
.home-page { padding: 20px; }
</style>第一个功能页面:直接使用 Store,文件:src/views/store/use.vue
<template>
<h2>使用 Store</h2>
<!-- 1. 直接在模板中使用 Store 的数据和计算属性 -->
<div>原始计数:{{ counterStore.count }}</div>
<div>双倍计数:{{ counterStore.doubleCount }}</div>
<!-- 2. 调用 Store 的方法 -->
<button @click="counterStore.increment">Increment</button>
</template>
<script setup lang="ts">
// 1. 导入定义好的 Store
import { useCounterStore } from '@/store/counter';
// 2. 创建 Store 实例(必须调用函数)
const counterStore = useCounterStore();
</script>
<style scoped>
button { margin: 10px; padding: 10px; background: #4CAF50; color: white; }
</style>核心知识点:
组件中使用 Store:先导入 → 调用函数获取实例 → 直接使用
模板中可直接访问 state/getters,调用 actions
数据是响应式的,修改后页面自动更新
解决响应式丢失
Store 是 reactive 包裹的对象,直接 ES6 解构会失去响应性:
// ❌ 错误写法:解构后 count 不是响应式
const { count } = counterStorestoreToRefs文件:src/views/store/deconstruction.vue
<template>
<h2>解构 Store</h2>
<!-- 解构后直接使用变量,更简洁 -->
<div>{{ count }}</div>
<div>{{ doubleCount }}</div>
<button @click="increment">Increment</button>
</template>
<script setup lang="ts">
import { storeToRefs } from 'pinia';
import { useCounterStore } from '@/store/counter';
const counterStore = useCounterStore();
// ✅ 正确:storeToRefs 把 state/getters 转为响应式 ref
const { count, doubleCount } = storeToRefs(counterStore);
// ⚠️ 注意:actions 不需要解构,直接调用
const increment = () => {
counterStore.increment();
};
</script>解释:
storeToRefs:专门用于解构 Store,保留响应式
只解构 state/getters,actions 直接用原实例调用
修改 State 有 3 种方式,$patch 批量修改性能最高。
文件:src/views/store/update.vue
<template>
<div>{{ counterStore.count }}</div>
<button @click="updateState">Update State</button>
</template>
<script setup lang="ts">
import { useCounterStore } from '@/store/counter';
const counterStore = useCounterStore();
const updateState = () => {
// 方式1:$patch 传入对象(批量修改简单数据)
counterStore.$patch({
count: counterStore.count + 1
});
// 方式2:$patch 传入函数(推荐!修改数组/对象等高代价操作)
// counterStore.$patch((state) => {
// state.count++;
// });
};
</script>对比:
直接修改:counterStore.count++ → 简单但多次修改性能差
$patch 对象:适合修改基础类型
$patch 函数:适合修改数组 / 对象(无需创建新集合,性能最优)
使用 $subscribe 监听 State 变化,比 watch 更高效(多状态修改只触发一次)。
文件:src/views/store/listening.vue
<template>
<div>{{ counterStore.count }}</div>
<button @click="counterStore.increment">Increment</button>
</template>
<script setup lang="ts">
import { useCounterStore } from '@/store/counter';
const counterStore = useCounterStore();
// 监听当前 Store 的所有状态变化
counterStore.$subscribe((mutation, state) => {
// mutation:变化信息(类型、修改内容)
console.log('变化类型:', mutation.type);
// state:最新的状态数据
console.log('最新状态:', state);
});
</script>解释:
mutation.type:direct / patch / action
适合日志记录、状态同步等场景
在 main.ts 中监听所有 Store,并把状态存到本地存储(刷新页面不丢失)。
// 在 main.ts 原有代码基础上添加
import { watch } from 'vue';
// 监听 Pinia 所有状态变化
watch(
pinia.state, // 监听根状态
(state) => {
// 持久化到 localStorage
localStorage.setItem('piniaState', JSON.stringify(state));
},
{ deep: true } // 深度监听
);因为 Store 解构用到了 ES6 对象解构,这里补充核心用法:
const obj = { name: '张三', age: 18 };
// 从对象中提取属性赋值给变量
const { name, age } = obj; const { name: userName } = obj;
// userName = '张三'function fn({ count }) { console.log(count) }
fn(counterStore)安装并挂载 Pinia(入口文件)
定义 Store(State/Getters/Actions)
配置路由 + 布局
基础使用 Store(组件中直接调用)
解构 Store(storeToRefs 保留响应式)
批量修改 State($patch 优化性能)
监听 State($subscribe / 全局监听)
状态持久化