5.2 Pinia 详解

一、核心功能详解

1. State:状态定义与操作
  • 定义 state
    使用函数返回初始状态,确保每个实例独立。

    state: () => ({
      user: null,
      isLoggedIn: false
    })
  • 访问与修改 state

    • 直接读取:store.count
    • 修改:store.count = 10 或 store.$patch() 批量更新
    // 批量更新
    store.$patch({
      count: 10,
      name: 'Alice'
    })
    
    // 使用函数形式(适用于复杂逻辑)
    store.$patch((state) => {
      state.count++
      state.name = 'Bob'
    })
  • 替换整个 state

    store.$state = { count: 0 }
2. Getters:派生状态
  • 支持参数、缓存、访问其他 getter

  • 第一个参数是 state

    getters: {
      // 基础 getter
      doneTodos: (state) => state.todos.filter(t => t.done),
    
      // 带参数的 getter
      getTodoById: (state) => (id) => state.todos.find(t => t.id === id)
    }

    使用:

    const todo = store.getTodoById(1)
3. Actions:业务逻辑中心
  • 可包含同步或异步操作

  • 可调用其他 action 或提交 state 变更

  • this 指向 store

    actions: {
      async fetchUser(id) {
        try {
          const res = await fetch(`/api/users/${id}`)
          this.user = await res.json()
          this.isLoggedIn = true
        } catch (error) {
          console.error('Fetch failed:', error)
        }
      },
    
      logout() {
        this.user = null
        this.isLoggedIn = false
      }
    }

二、高级特性

1. Store 之间的相互调用
// stores/user.js
export const useUserStore = defineStore('user', { ... })

// stores/cart.js
export const useCartStore = defineStore('cart', {
  actions: {
    async checkout() {
      const user = useUserStore()
      if (!user.isLoggedIn) {
        throw new Error('Please log in first')
      }
      // 执行结算逻辑
    }
  }
})

⚠️ 注意:避免循环依赖。

2. 持久化插件(pinia-plugin-persistedstate)

自动将状态持久化到 localStorage。

npm install pinia-plugin-persistedstate
// main.js
import { createPinia } from 'pinia'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'

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

在 store 中启用:

export const useCartStore = defineStore('cart', {
  state: () => ({
    items: []
  }),
  persist: true // 或配置具体选项
})

如果是选项api

配置选项

3. 插件系统(自定义插件)

可用于日志、持久化、事件追踪等。

pinia.use(({ store }) => {
  // 添加自定义属性
  store.$reset = () => {
    store.$state = store.$options.state()
  }

  // 响应状态变化
  store.$subscribe((mutation, state) => {
    console.log('State changed:', mutation, state)
  })
})

三、注意事项与最佳实践

✅ 推荐做法
  1. 按功能拆分 Store
    useUserStoreuseCartStore,避免单一巨型 store。

  2. 使用 TypeScript 提升类型安全

    export const useUserStore = defineStore('user', {
      state: (): UserState => ({
        user: null
      }),
      getters: {
        fullName: (state): string => state.user?.name || ''
      }
    })
  3. 避免在 getters 中执行副作用
    getters 应是纯函数,不应修改状态或发起请求。

  4. Action 中处理异步逻辑
    所有 API 调用、复杂逻辑应放在 actions 中。

  5. 合理使用 $patch 批量更新
    减少不必要的渲染。

⚠️ 常见陷阱
  1. 解构 store 会失去响应性
    ❌ 错误:

    const { count, increment } = useCounterStore()
    // count 不再响应式

    ✅ 正确:

    const store = useCounterStore()
    // 使用 store.count
  2. 不要直接替换 state 引用(除非用 $state

    // ❌ 错误
    this.state = { count: 10 }
    // ✅ 正确
    this.$state = { count: 10 }
  3. 避免在 setup 外使用 store(SSR 注意)
    setup()onMounted 中调用 useStore()

  4. 热重载配置(开发环境)

    if (import.meta.hot) {
      import.meta.hot.accept(acceptHMRUpdate(useCounterStore, import.meta.hot))
    }
  5. TypeScript 类型推断问题
    使用 defineStore 时,建议显式定义类型或使用返回类型推断。

四、与 Vuex 的对比

特性PiniaVuex
模块系统自然模块化需手动注册模块
Mutations无(直接修改)必须通过 mutations
Actions支持同步/异步同上
TypeScript原生支持需额外配置
API 风格Composition 风格选项式为主
体积更小相对较大

五、组合式 API 风格的基本语法

使用 defineStore() 并传入一个函数,该函数内部使用 refcomputedwatch 等响应式 API 来组织状态和逻辑。

// stores/counter.ts
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'

export const useCounterStore = defineStore('counter', () => {
  // state: 使用 ref 定义响应式状态
  const count = ref(0)
  const name = ref('Vue User')

  // getters: 使用 computed 定义派生状态
  const doubleCount = computed(() => count.value * 2)
  const welcomeMessage = computed(() => `Hello, ${name.value}!`)

  // actions: 定义方法(可同步或异步)
  function increment() {
    count.value++
  }

  function decrement() {
    count.value--
  }

  function $reset() {
    count.value = 0
  }

  async function fetchUser() {
    const res = await fetch('/api/user')
    const user = await res.json()
    name.value = user.name
  }

  // 必须 return 暴露给外部使用的状态和方法
  return {
    // state
    count,
    name,
    // getters
    doubleCount,
    welcomeMessage,
    // actions
    increment,
    decrement,
    fetchUser,
    $reset
  }
})

注意:在组合式 API 风格中,必须显式 return 你希望在组件中使用的变量和函数,否则无法访问。


六、在组件中使用组合式 Store

1. 使用 <script setup>(推荐)

<script setup>
import { useCounterStore } from '@/stores/counter'

// 使用 store
const counter = useCounterStore()
</script>

<template>
  <div>
    <h2>{{ counter.welcomeMessage }}</h2>
    <p>Count: {{ counter.count }}</p>
    <p>Double: {{ counter.doubleCount }}</p>
    <button @click="counter.increment">+1</button>
    <button @click="counter.decrement">-1</button>
    <button @click="counter.fetchUser">Fetch User</button>
    <button @click="counter.$reset">Reset</button>
  </div>
</template>

2. 解构 Store(保持响应性)

由于 counter.count 是一个 ref,在模板中可以直接使用。但在 JS 中解构时,需要使用 storeToRefs 来保持响应性

<script setup>
import { useCounterStore } from '@/stores/counter'
import { storeToRefs } from 'pinia'

const counter = useCounterStore()

// ❌ 错误:直接解构会失去响应性
// const { count, doubleCount } = counter

// ✅ 正确:使用 storeToRefs
const { count, doubleCount, name } = storeToRefs(counter)
const { increment, decrement } = counter // actions 不需要 ref 包装

// 现在可以在 setup 中安全使用
console.log(count.value) // 0
</script>

🔥 storeToRefs 只包装 state 和 getters,不包装 actions,因为 actions 是方法。

Pinia 是 Vue 3 状态管理的未来。它通过简洁的 API、强大的 TypeScript 支持和灵活的架构,极大提升了开发效率和代码可维护性。

建议:

  • 新项目直接使用 Pinia
  • 老项目可逐步从 Vuex 迁移到 Pinia
  • 结合插件生态(如持久化、日志)提升功能

Pinia 不仅是一个状态管理库,更是 Vue 3 开发哲学的体现:简洁、灵活、高效

03-08
### Pinia Vue.js 状态管理库使用指南 #### 安装 Pinia 为了在项目中集成 Pinia,可以通过 npm 或 yarn 进行安装: ```bash npm install pinia ``` 或者 ```bash yarn add pinia ``` 完成安装之后,在创建 Vue 应用实例之前初始化 Pinia。 #### 初始化并配置 Pinia 下面展示如何在一个新项目里设置 Pinia: ```javascript import { createApp } from 'vue' import { createPinia } from 'pinia' const app = createApp(App) // 创建一个新的 Pinia 实例 app.use(createPinia()) app.mount('#app') ``` 这段代码展示了怎样引入 `createPinia` 函数,并将其作为插件注册到 Vue 应用程序中[^1]。 #### 定义 Store (商店) 定义一个简单的 store 可以通过调用 `defineStore` 方法来实现。这里有一个计数器的例子: ```typescript import { defineStore } from 'pinia' export const useCounterStore = defineStore('counter', { state: () => ({ count: 0, }), actions: { increment() { this.count++ }, }, }) ``` 此片段说明了如何利用 `defineStore` 来构建一个具有基本功能的 store,其中包含了状态(state)和动作(actions)。 #### 访问 Store 数据 组件内部访问 store 非常简单,只需要导入相应的 store 并调用即可: ```html <template> <div>{{ counter.count }}</div> <button @click="increment">Increment</button> </template> <script setup> import { useCounterStore } from './stores/counter' const counter = useCounterStore() function increment() { counter.increment() } </script> ``` 上述模板显示了如何在单文件组件(SFCs)内获取 store 的数据以及触发其方法。 #### 查看 Pinia Store 在 Vue Devtools 中的表现 一旦设置了 Pinia stores 后,就可以借助 Vue Devtools 工具查看这些 store 的实时变化情况。打开浏览器中的开发者工具,切换至 Vue 标签页就能看到所有已定义好的 store 列表及其当前的状态值。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

chxii

小小打赏,大大鼓励!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值