一、什么是pinia?
在 Vue3 生态中,状态管理一直是开发者关注的核心话题。随着 Vuex 的逐步淡出,Pinia 作为官方推荐的状态管理库,凭借其简洁的 API、强大的功能和对 Vue3 特性的完美适配,成为了新时代的不二之选。今天我们就来深入探讨 Pinia 的使用方法和最佳实践。
如果用生活中的例子来解释:
Pinia 就像一个 “共享冰箱”
想象一下,你和室友合租一套公寓,你们有一个 共享冰箱(Pinia Store)。这个冰箱的作用是:
- 存放公共物品(状态 State):比如牛奶、水果、饮料等。
- 规定取用规则(Getters):比如 “只能在早餐时间喝牛奶”。
- 处理特殊操作(Actions):比如 “牛奶喝完后要通知所有人”。
pinia官网:Pinia | The intuitive store for Vue.js
二、怎么创建一个Pinia
1. 创建项目的时候直接选择Pinia
2. 项目中没有Pinia时,手动下载
①安装Pinia
npm install pinia
②在src中创建stores
③创建ts文件作为Pinia容器
④在counter.ts中加入以下代码
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', () => {
})
1、defineStore 是 Pinia 状态管理库 中的一个核心函数
2、参数说明
第一个参数 'counter':store 的唯一标识符(不可重复)
第二个参数 () => {}:store 的配置函数,返回 store 的状态和方法
⑤在main.ts中挂载Pinia
import './assets/main.css'
import { createApp } from 'vue'
import { createPinia } from 'pinia' // 引入创建Pinia方法
import App from './App.vue'
const app = createApp(App)
app.use(createPinia()) // 挂载到app页面
app.mount('#app')
这样就成功手动实现了Pinia的手动安转和配置了。
三、定义第一个Pinia
1、第一个选项式Pinia示例
基础语法:
import { defineStore } from 'pinia'
// 定义并导出 Store
export const defineStore实例化对象名 = defineStore('状态管理对象名称', {
// 状态初始化,相当于data
state: () => ({
属性名1:属性值1
}),
// 计算属性(类似 Vue 组件中的 computed)
getters: {
},
// 方法(类似 Vue 组件中的 methond)
actions: {
函数
}
})
基础示例:
/stores/counter.ts
// 1、导入defineStore
import { defineStore } from 'pinia'
import {computed,ref} from "vue";
export const useCounterStore = defineStore('counter', {
state() { // 相当于组件中的data
return {
title:"选项式计数管理器",
count: 25 // 状态管理变量数据
}
},
getters: { // 相当于组件中的computed
doubleCount: (state) => state.count * 2, // 2倍计算属性数据
doubleCountPlusOne: (state) => state.count + 10 // 加10计算属性数据
},
actions: { // 相当于组件中的methods
increment() {
this.count++ // 创建函数每次点击按钮,count加1
},
decrement(){
this.count-- // 创建函数每次点击按钮,count减1
}
}
})
/src/App.vue
<script setup lang="ts">
// 1、导入
import {useCounterStore} from "@/stores/counter.ts"
// 2、实例化
const counterStore = useCounterStore()
</script>
<template>
// 3、使用
<div class="app">
<h2>标题{{counterStore.title}}</h2>
<p>当前计数:{{counterStore.count}}</p>
<p>双倍计数:{{counterStore.doubleCount}}</p>
<p>计数+10:{{counterStore.doubleCountPlusOne}}</p>
<button @click="counterStore.increment()">点击+1</button>
<button @click="counterStore.decrement()">点击-1</button>
</div>
</template>
运行结果:
2、第一个组合式Pinia实例
基础语法:
import { defineStore } from 'pinia'
import { ref, computed, reactive } from 'vue'
export const useStoreNameStore = defineStore('storeId', () => {
// 1. State - 使用 ref 或 reactive
const count = ref(0)
const state = reactive({
name: 'example'
})
// 2. Getters - 使用 computed
const doubleCount = computed(() => count.value * 2)
// 3. Actions - 普通函数
function increment() {
count.value++
}
// 4. 返回需要暴露的属性和方法
return {
count,
doubleCount,
increment
}
})
基础示例:
/stores/users.ts
import {defineStore} from 'pinia'
import {computed, reactive, ref} from "vue";
export const useUserStore = defineStore('user', ()=>{
let isLogin = ref(false);
let username = ref('未知');
let email = ref('未知');
let displayName = ref('未知');
let roles = reactive(['管理员','用户','玩家','游客']);
let nowRole = ref('未知');
let theme = ref('白色');
let language = ref('chinese');
let message = ref(0);
function updateLoginStatus(){
if(isLogin.value){
isLogin.value = !isLogin.value;
username.value = "未知";
displayName.value = "未知";
nowRole.value = "未知";
message.value = 0;
email.value = "未知";
}
else{
isLogin.value = !isLogin.value;
username.value = "张三";
displayName.value = "追风少年";
let random:number = Math.floor(Math.random()*4);
nowRole.value = roles[random];
message.value = 10;
email.value = "zhangsan@163.com";
}
}
function updateUserProfile(){
theme.value = theme.value === 'white' ? 'dark' : 'white';
language.value = language.value === 'chinese' ? 'english' : 'chinese';
}
function resetUser(){
username.value = "未知";
displayName.value = "未知";
nowRole.value = "未知";
message.value = 0;
roles.splice(0,roles.length);
email.value = "未知";
}
return {
isLogin,
username,
email,
displayName,
nowRole,
theme,
language,
message,
updateLoginStatus,
updateUserProfile,
resetUser
}
})
/src/App.vue
<script setup lang="ts">
import { useUserStore } from "@/stores/users.ts";
const userStore = useUserStore();
</script>
<template>
<div class="user-profile">
<h1>用户资料</h1>
<!-- 直接访问 -->
<p>用户名: {{ userStore.username }}</p>
<p>网名: {{ userStore.displayName }}</p>
<!-- 解构访问 -->
<div>
<p>邮箱: {{userStore.email }}</p>
<p>登录状态: {{ userStore.isLogin ? '登录':'未登录'}}</p>
</div>
<!-- 复杂数据访问 -->
<div>
<p>主题: {{ userStore.theme }}</p>
<p>语言: {{ userStore.language }}</p>
</div>
<!-- 数组数据 -->
<div>
<p>角色: {{ userStore.nowRole }}</p>
<p>未读通知: {{ userStore.message}}</p>
</div>
<button @click="userStore.updateLoginStatus" >{{ userStore.isLogin ? '退出':'登录'}}</button>
<button @click="userStore.updateUserProfile">更新资料</button>
<button @click="userStore.resetUser">重置用户</button>
</div>
</template>
运行结果:
五、综合案例
/stores/counter.ts
import {reactive} from 'vue'
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', () => {
let products = reactive(
[
{
id: 1, // 商品ID
name: "苹果16ProMax", // 商品名称
price: 12999, // 商品价格
category: "phone", // 商品类别
num:123, // 商品库存数量
rating: 4.8, // 商品评分
},
{
id: 2,
name: "联想拯救者",
price: 23999,
category: "laptop", // 笔记本
num: 0,
rating: 3.8,
},
{
id: 3,
name: "华硕天选6pro",
price: 11499,
category: "laptop", // 笔记本
num: 15,
rating: 4.9,
},
{
id: 3,
name: "iQoo平板7",
price: 3499,
category: "tablet", // 平板电脑
num: 899,
rating: 3.7,
},
{
id: 4,
name: "iPad Air",
price: 8599,
category: "tablet", // 平板电脑
num: 899,
rating: 4.1,
},
{
id: 5,
name: "小米手环7",
price: 999,
category: "watch", // 手表
num:45,
rating: 4.61,
},
{
id: 6,
name: "苹果手表6",
price: 3888,
category: "watch", // 手表
num:45,
rating: 4.9,
},
{
id: 6,
name: "小米手机",
price: 3999,
category: "phone",
num:425,
nun: 442,
rating: 4.7,
},
],
)
let avgrating = products.reduce((sum,crr) => sum+crr.rating,0) / products.length
let avgprice = products.reduce((sum,crr) => sum+crr.price,0) / products.length
let sumNum = products.reduce((sum,crr) => sum+crr.num,0)
let stockNum = products.filter(item=>item.num <= 0).length
let categories = reactive(["phone", "laptop", "tablet", "watch"])
return {
products,
categories,
avgrating,
avgprice,
sumNum,
stockNum
}
})
/src/App.vue
<script setup lang="ts">
import {useCounterStore} from './stores/counter.ts'
import {computed, ref} from 'vue'
const store = useCounterStore()
const products = store.products
const minPrice = ref(0)
const maxPrice = ref(0)
const filteredProducts = computed(() => {
return products.filter(
item => item.price >= minPrice.value && item.price <= maxPrice.value
)
})
</script>
<template>
<div class="container">
<h1>库存管理系统</h1>
<h2>总产品数据</h2>
<table>
<tr>
<th>商品名</th>
<th>商品价格</th>
<th>商品库存</th>
<th>商品评分</th>
</tr>
<tr v-for="(item) in products">
<td>{{item.name}}</td>
<td>{{item.price}}</td>
<td>{{item.num}}</td>
<td>{{item.rating}}</td>
</tr>
</table>
<strong>产品数量:{{store.sumNum}}</strong>
<strong>缺货商品数量:{{store.stockNum}}</strong>
<strong>平均价格:{{store.avgprice}}</strong>
<strong>平均评分:{{store.avgrating}}</strong>
<h2>分类统计</h2>
<div class="classify" v-for="item in store.categories">
<strong>{{item}}</strong>
<table>
<tr>
<th>商品名</th>
<th>商品价格</th>
<th>商品库存</th>
<th>商品评分</th>
</tr>
<template v-for="product in products" :key="product.id">
<tr v-if="product.category === item">
<td>{{ product.name }}</td>
<td>{{ product.price }}</td>
<td>{{ product.num }}</td>
<td>{{ product.rating }}</td>
</tr>
</template>
</table>
</div>
<h2>商品筛选</h2>
<div>
<label>价格区间:</label>
<input type="number" v-model="minPrice">---<input type="number" v-model="maxPrice">
<table v-if="filteredProducts.length > 0">
<tr>
<th>商品名</th>
<th>商品价格</th>
<th>商品库存</th>
<th>商品评分</th>
</tr>
<tr v-for="item in filteredProducts" :key="item.id">
<td>{{ item.name }}</td>
<td>{{ item.price }}</td>
<td>{{ item.num }}</td>
<td>{{ item.rating }}</td>
</tr>
</table>
<p v-else>暂无符合条件的商品。</p>
</div>
</div>
</template>
<style scoped>
.container{
width:1000px;
padding: 20px;
border: #6e7681 1px solid;
box-shadow: 2px 2px 4px #bdc1c6;
margin: 0 auto;
}
h1{
text-align: center;
}
table{
margin: 0 auto;
border: #6e7681 1px solid;
width:1000px;
border-collapse: collapse; /* 合并边框 */
margin-bottom: 30px;
}
strong{
display: block;
margin-top: 10px;
}
th,tr,td{
border: #6e7681 1px solid; /* 关键:为单元格添加边框 */
padding: 10px; /* 添加内边距使内容更美观 */
text-align: center;
}
</style>
运行结果: