Pinia状态管理Composition API设计与TypeScript类型推导实战

Pinia替代Vuex的设计思路与核心优势

Pinia是Vue3官方推荐的状态管理库,相比Vuex简化了类型推导、去除了mutations层、支持Composition API风格定义store。Pinia的API设计更贴近Vue3的setup函数写法,开发者可以直接使用ref、computed等响应式API,无需在getters和mutations间来回切换。

Pinia与Vuex的核心差异:Pinia没有mutations概念,状态修改直接在actions中操作;Pinia的store是扁平结构,没有modules嵌套,每个store独立管理;Pinia对TypeScript的类型推导开箱即用,泛型配置更少。

Store定义与Composition API用法

使用Setup Store语法(Composition API风格)定义一个用户管理store:

import { defineStore } from 'pinia'
import { ref, computed } from 'vue'

export const useUserStore = defineStore('user', () => {
  // state
  const token = ref<string | null>(null)
  const userInfo = ref<UserInfo | null>(null)
  const permissions = ref<string[]>([])

  // getters
  const isLoggedIn = computed(() => !!token.value)
  const hasPermission = computed(() => {
    return (perm: string) => permissions.value.includes(perm)
  })
  const displayName = computed(() => {
    return userInfo.value?.nickname || userInfo.value?.username || 'Guest'
  })

  // actions
  async function login(credentials: LoginParams) {
    const res = await authApi.login(credentials)
    token.value = res.token
    userInfo.value = res.user
    permissions.value = res.permissions
    localStorage.setItem('token', res.token)
  }

  function logout() {
    token.value = null
    userInfo.value = null
    permissions.value = []
    localStorage.removeItem('token')
  }

  async function fetchProfile() {
    if (!token.value) return
    const res = await authApi.getProfile()
    userInfo.value = res
    permissions.value = res.permissions
  }

  return {
    token, userInfo, permissions,
    isLoggedIn, hasPermission, displayName,
    login, logout, fetchProfile
  }
})

TypeScript类型定义与接口约束

为store定义完整的TypeScript类型,确保类型安全:

// types/user.ts
interface UserInfo {
  id: number
  username: string
  nickname: string
  avatar: string
  email: string
  role: 'admin' | 'editor' | 'viewer'
  permissions: string[]
}

interface LoginParams {
  username: string
  password: string
  captcha?: string
}

interface LoginResponse {
  token: string
  user: UserInfo
  permissions: string[]
  expiresIn: number
}

export type { UserInfo, LoginParams, LoginResponse }

在组件中使用store并保持类型推导:

<script setup lang="ts">
import { useUserStore } from '@/stores/user'
import { storeToRefs } from 'pinia'

const userStore = useUserStore()

// storeToRefs保持响应性,类型自动推导
const { isLoggedIn, displayName, userInfo } = storeToRefs(userStore)

// actions可以直接解构
const { login, logout } = userStore

// 调用时参数有类型检查
async function handleLogin() {
  await login({
    username: 'admin',
    password: '123456'
  })
}
</script>

多Store组合与跨Store调用

Pinia支持在一个store中调用另一个store,实现模块间状态共享:

// stores/cart.ts
import { defineStore } from 'pinia'
import { useUserStore } from './user'
import { computed, ref } from 'vue'

export const useCartStore = defineStore('cart', () => {
  const items = ref<CartItem[]>([])

  const total = computed(() =>
    items.value.reduce((sum, item) => sum + item.price * item.quantity, 0)
  )

  // 跨store调用
  function checkout() {
    const userStore = useUserStore()
    if (!userStore.isLoggedIn) {
      throw new Error('Need login first')
    }

    const order = {
      userId: userStore.userInfo!.id,
      items: items.value,
      total: total.value
    }

    return orderApi.create(order)
  }

  return { items, total, checkout }
})

持久化插件集成

使用pinia-plugin-persistedstate实现store状态持久化到localStorage:

// main.ts
import { createPinia } from 'pinia'
import piniaPersist from 'pinia-plugin-persistedstate'

const pinia = createPinia()
pinia.use(piniaPersist)

app.use(pinia)

// stores/user.ts - 在store定义中添加persist选项
export const useUserStore = defineStore('user', () => {
  // ... store logic
  return { /* ... */ }
}, {
  persist: {
    key: 'app-user',
    storage: localStorage,
    paths: ['token', 'userInfo', 'permissions']
  }
})

persist.paths指定需要持久化的字段,避免将临时状态写入存储。对于敏感数据(如token),建议配合加密库进行加密后存储。

SSR同构与状态水合

Nuxt3或Vite SSR场景下,Pinia需要在服务端预取数据后序列化传输到客户端进行水合:

// 服务端入口
import { createPinia } from 'pinia'
const pinia = createPinia()
app.use(pinia)

// 预取数据
const userStore = useUserStore(pinia)
await userStore.fetchProfile()

// 序列化state到HTML
const state = JSON.stringify(pinia.state.value)

// 客户端入口 - 水合
if (typeof window !== 'undefined') {
  const pinia = createPinia()
  pinia.state.value = JSON.parse(window.__PINIA_STATE__)
  app.use(pinia)
}

SSR场景下避免在setup顶层直接调用store的async action,应在组件的onServerPrefetch或async setup中调用,确保服务端渲染时数据已就绪。

原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/pinia-zhuang-tai-guan-li-compositionapi-she-ji-yu/

(0)
小编小编
上一篇 2小时前
下一篇 2小时前

相关推荐