vue3+ts 中使用pinia状态管理

下面详细介绍在 Vue3 + TypeScript 项目中使用 Pinia ^3.0.4 的完整步骤和最佳实践,包含核心概念、代码示例和常见场景。

一、环境准备

确保项目已安装 Vue3 + TypeScript,然后安装 Pinia:

bash

运行

perl 复制代码
# npm
npm install pinia@^3.0.4

# yarn
yarn add pinia@^3.0.4

# pnpm
pnpm add pinia@^3.0.4

二、初始化 Pinia

在项目入口文件(如 main.ts)中创建并挂载 Pinia 实例:

typescript

运行

javascript 复制代码
// main.ts
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'

const app = createApp(App)
// 创建 Pinia 实例
const pinia = createPinia()
// 挂载到 Vue 应用
app.use(pinia)

app.mount('#app')

三、核心概念:定义 Store

Pinia 中只有 store 概念(替代 Vuex 的 State/Mutation/Action/Getter),通过 defineStore 定义,推荐按功能模块划分 store。

3.1 基础示例(用户 Store)

创建 src/stores/user.ts

typescript

运行

typescript 复制代码
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'

// 第一个参数:store 唯一 ID(必须唯一)
// 第二个参数:store 配置对象
export const useUserStore = defineStore('user', () => {
  // ========== 1. 状态(State):用 ref/reactive 定义 ==========
  const name = ref<string>('张三')
  const age = ref<number>(20)
  const permissions = ref<string[]>(['read', 'write'])

  // ========== 2. 计算属性(Getters):用 computed 定义 ==========
  // 注意:getter 依赖状态自动缓存,类似 Vue 组件的 computed
  const fullInfo = computed(() => {
    return `姓名:${name.value},年龄:${age.value}`
  })

  const hasAdminPermission = computed(() => {
    return permissions.value.includes('admin')
  })

  // ========== 3. 方法(Actions):普通函数(支持同步/异步) ==========
  // 同步 Action
  function updateName(newName: string) {
    name.value = newName
  }

  // 异步 Action(示例:模拟接口请求)
  async function fetchUserInfo(userId: number) {
    try {
      // 模拟 API 请求
      const res = await new Promise<{ name: string; age: number }>((resolve) => {
        setTimeout(() => {
          resolve({ name: '李四', age: 25 })
        }, 1000)
      })
      name.value = res.name
      age.value = res.age
    } catch (error) {
      console.error('获取用户信息失败:', error)
    }
  }

  // ========== 4. 暴露状态/计算属性/方法 ==========
  return {
    name,
    age,
    permissions,
    fullInfo,
    hasAdminPermission,
    updateName,
    fetchUserInfo
  }
})

3.2 类型提示说明

  • Pinia 结合 TypeScript 会自动推导类型,无需手动声明(如 name 自动推导为 Ref<string>)。
  • 如果需要显式指定类型,可在 ref/computed 中明确标注(如上例)。

四、在组件中使用 Store

4.1 基础使用(组合式 API)

vue

xml 复制代码
<!-- src/components/UserInfo.vue -->
<template>
  <div>
    <h3>{{ userStore.fullInfo }}</h3>
    <p>是否有管理员权限:{{ userStore.hasAdminPermission ? '是' : '否' }}</p>
    <button @click="handleUpdateName">修改姓名</button>
    <button @click="handleFetchUser">异步获取用户信息</button>
  </div>
</template>

<script setup lang="ts">
// 导入定义的 store
import { useUserStore } from '@/stores/user'

// 获取 store 实例(Pinia 会自动管理单例,多次调用返回同一个实例)
const userStore = useUserStore()

// 方法示例
const handleUpdateName = () => {
  userStore.updateName('王五')
}

const handleFetchUser = async () => {
  await userStore.fetchUserInfo(1)
}
</script>

4.2 解构 Store(保持响应式)

直接解构 store 会丢失响应式,需使用 storeToRefs

typescript

运行

javascript 复制代码
import { storeToRefs } from 'pinia'
import { useUserStore } from '@/stores/user'

const userStore = useUserStore()
// 解构状态/计算属性(保持响应式)
const { name, age, fullInfo } = storeToRefs(userStore)
// 方法可直接解构(无需 ref)
const { updateName } = userStore

// 使用
console.log(name.value) // 响应式
updateName('赵六') // 正常调用

五、高级用法

5.1 重置 Store 状态

调用 $reset() 方法重置状态到初始值:

typescript

运行

scss 复制代码
const userStore = useUserStore()
userStore.$reset() // 重置所有状态

5.2 批量修改状态

使用 $patch 方法批量更新(性能更优):

typescript

运行

javascript 复制代码
// 方式1:对象形式(适合简单更新)
userStore.$patch({
  name: '钱七',
  age: 30
})

// 方式2:函数形式(适合复杂更新)
userStore.$patch((state) => {
  state.permissions.push('admin')
  state.age += 1
})

5.3 监听 Store 变化

使用 $subscribe 监听状态变化(类似 Vue 的 watch):

typescript

运行

javascript 复制代码
const userStore = useUserStore()
// 监听所有状态变化
const unsubscribe = userStore.$subscribe((mutation, state) => {
  console.log('状态变化:', mutation, state)
  // mutation.type:'direct'(直接修改)| 'patch object' | 'patch function'
  // mutation.storeId:store 唯一 ID
})

// 取消监听(组件卸载时调用)
onUnmounted(() => {
  unsubscribe()
})

// 单独监听某个属性(用 Vue 的 watch)
watch(() => userStore.age, (newAge, oldAge) => {
  console.log('年龄变化:', newAge, oldAge)
})

5.4 Store 之间相互调用

一个 store 可以导入并使用另一个 store:

typescript

运行

typescript 复制代码
// src/stores/cart.ts
import { defineStore } from 'pinia'
import { useUserStore } from './user'

export const useCartStore = defineStore('cart', () => {
  const userStore = useUserStore()
  const cartList = ref<Array<{ id: number; name: string }>>([])

  // 根据用户权限过滤购物车
  const filteredCart = computed(() => {
    if (userStore.hasAdminPermission) {
      return cartList.value
    }
    return cartList.value.filter(item => item.id < 10)
  })

  function addToCart(item: { id: number; name: string }) {
    // 使用 userStore 的状态
    if (userStore.age >= 18) {
      cartList.value.push(item)
    }
  }

  return { cartList, filteredCart, addToCart }
})

5.5 持久化存储(可选)

结合 pinia-plugin-persistedstate 实现状态持久化(如 localStorage):

bash

运行

bash 复制代码
# 安装插件
npm install pinia-plugin-persistedstate

typescript

运行

javascript 复制代码
// main.ts
import { createPinia } from 'pinia'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'

const pinia = createPinia()
// 注册持久化插件
pinia.use(piniaPluginPersistedstate)

typescript

运行

javascript 复制代码
// 修改 user store,添加持久化配置
export const useUserStore = defineStore('user', () => {
  // ... 原有代码
}, {
  // 持久化配置
  persist: {
    key: 'user-store', // 自定义存储的 key
    storage: localStorage, // 存储方式(localStorage/sessionStorage)
    paths: ['name', 'age'] // 只持久化 name 和 age,默认所有状态
  }
})

六、类型扩展(可选)

如果需要扩展 Pinia 的类型(如自定义插件),可创建 src/types/pinia.d.ts

typescript

运行

typescript 复制代码
import 'pinia'

declare module 'pinia' {
  export interface PiniaCustomProperties {
    // 扩展 store 实例的属性/方法
    $log: () => void
  }
}

// 在 main.ts 中注册
pinia.use(({ store }) => {
  store.$log = () => {
    console.log(`[${store.$id}]`, store.$state)
  }
})

// 使用
userStore.$log() // 打印 store 状态

七、注意事项

  1. Store ID 唯一性defineStore 的第一个参数必须唯一,否则会导致状态冲突。
  2. 避免在非组件中滥用 Store:如果在工具函数中使用 Store,确保 Pinia 已初始化。
  3. 异步 Action 错误处理:异步 Action 需手动捕获异常,避免页面崩溃。
  4. 响应式保持 :解构 Store 状态时必须使用 storeToRefs,方法可直接解构。

以上就是 Vue3 + TypeScript 中使用 Pinia ^3.0.4 的完整指南,涵盖了基础用法、高级特性和最佳实践,可根据项目需求灵活调整。

相关推荐
Dgua1 小时前
一文吃透Vue Diff原理:从核心逻辑到实战避坑
前端·vue.js
小飞侠在吗1 小时前
vue Hooks
前端·javascript·vue.js
龙亘川1 小时前
开箱即用的智慧城市一网统管 AI 平台——项目目录结构及前端结构(7-9)
前端·人工智能·智慧城市
多多1531 小时前
基于大模型的文档自动化测试用户提交文件进行文档测试
前端
张风捷特烈1 小时前
Flutter TolyUI 框架#11 | 标签 tolyui_tag
前端·flutter·ui kit
梵得儿SHI1 小时前
Vue 核心语法深度解析:生命周期与响应式之计算属性(computed)与侦听器(watch/watchEffect)
前端·javascript·vue.js·计算属性·侦听器·缓存机制·数据派生
anuoua1 小时前
歼20居然是个框架-基于 Signals 信号的前端框架设计
前端·javascript·前端框架
秋天的一阵风1 小时前
翻掘金看到停更的前辈们,突然想聊两句 🤔
前端·vue.js·程序员
中杯可乐多加冰1 小时前
openEuler软件生态体验:快速部署Nginx Web服务器
服务器·前端·nginx