第8章 状态管理

第22节 Pinia基础

8.22.1 Pinia安装与配置

什么是Pinia?

Pinia是Vue官方推荐的状态管理库,用于替代Vuex 4。它提供了更简洁的API、更好的TypeScript支持和更灵活的结构,同时保留了Vuex的核心概念。

安装Pinia

使用npm安装:

npm install pinia

使用yarn安装:

yarn add pinia

使用pnpm安装:

pnpm add pinia

配置Pinia

在Vue应用中配置Pinia非常简单,只需要在main.jsmain.ts中创建并使用Pinia实例:

// main.js
import { createApp } from 'vue'
import App from './App.vue'
import { createPinia } from 'pinia' // 导入createPinia函数

// 创建Pinia实例
const pinia = createPinia()

// 创建Vue应用实例
const app = createApp(App)

// 使用Pinia
app.use(pinia)

// 挂载应用
app.mount('#app')

配置文件结构

推荐的文件结构如下:

src/
├── stores/          # 存放所有Store
│   ├── counter.js   # 计数器Store示例
│   ├── user.js      # 用户Store示例
│   └── index.js     # Store出口文件(可选)
├── main.js          # 应用入口
└── App.vue          # 根组件

8.22.2 Store定义与使用

创建第一个Store

使用defineStore函数创建Store,它接收两个参数:

  1. Store的唯一名称(字符串)
  2. Store配置对象,包含stategettersactions
// stores/counter.js
import { defineStore } from 'pinia'

// 定义并导出Store
export const useCounterStore = defineStore('counter', {
  // 状态:返回初始状态对象的函数
  state: () => ({
    count: 0,
    name: 'Eduardo',
    isAdmin: false,
    items: []
  }),

  // Getters:计算属性
  getters: {
    // 简单Getters
    doubleCount: (state) => state.count * 2,
    
    // 使用this访问其他Getters
    doubleCountPlusOne() {
      return this.doubleCount + 1
    },
    
    // 带参数的Getters
    getItemById: (state) => (id) => {
      return state.items.find(item => item.id === id)
    }
  },

  // Actions:修改状态的方法(可以是异步的)
  actions: {
    // 同步Action
    increment() {
      this.count++
    },
    
    // 带参数的Action
    incrementBy(value) {
      this.count += value
    },
    
    // 异步Action
    async fetchItems() {
      // 模拟API请求
      const response = await new Promise(resolve => {
        setTimeout(() => {
          resolve([
            { id: 1, name: 'Item 1' },
            { id: 2, name: 'Item 2' },
            { id: 3, name: 'Item 3' }
          ])
        }, 1000)
      })
      
      this.items = response
    },
    
    // 使用其他Actions
    async refreshItems() {
      await this.fetchItems()
      // 可以添加其他逻辑
    }
  }
})

在组件中使用Store

在组件中使用Store非常简单,只需要导入并调用我们定义的Store函数即可:

选项式API中使用
<template>
  <div>
    <h1>{{ counterStore.name }}</h1>
    <p>Count: {{ counterStore.count }}</p>
    <p>Double Count: {{ counterStore.doubleCount }}</p>
    <p>Double Count Plus One: {{ counterStore.doubleCountPlusOne }}</p>
    
    <button @click="counterStore.increment">Increment</button>
    <button @click="counterStore.incrementBy(5)">Increment by 5</button>
    <button @click="counterStore.fetchItems">Fetch Items</button>
    
    <ul>
      <li v-for="item in counterStore.items" :key="item.id">
        {{ item.name }}
      </li>
    </ul>
  </div>
</template>

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

export default {
  data() {
    return {
      counterStore: useCounterStore()
    }
  }
}
</script>
组合式API中使用
<template>
  <div>
    <h1>{{ counter.name }}</h1>
    <p>Count: {{ counter.count }}</p>
    <p>Double Count: {{ counter.doubleCount }}</p>
    <p>Double Count Plus One: {{ counter.doubleCountPlusOne }}</p>
    
    <button @click="counter.increment">Increment</button>
    <button @click="counter.incrementBy(5)">Increment by 5</button>
    <button @click="fetchItems">Fetch Items</button>
    
    <ul>
      <li v-for="item in counter.items" :key="item.id">
        {{ item.name }}
      </li>
    </ul>
  </div>
</template>

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

// 获取Store实例
const counter = useCounterStore()

// 调用异步Action
const fetchItems = async () => {
  await counter.fetchItems()
}
</script>

使用storeToRefs保持响应性

当我们需要解构Store中的状态时,可以使用storeToRefs函数来保持响应性:

<template>
  <div>
    <h1>{{ name }}</h1>
    <p>Count: {{ count }}</p>
    <p>Double Count: {{ doubleCount }}</p>
    
    <button @click="increment">Increment</button>
  </div>
</template>

<script setup>
import { useCounterStore } from '../stores/counter'
import { storeToRefs } from 'pinia' // 导入storeToRefs

// 获取Store实例
const counter = useCounterStore()

// 解构状态并保持响应性
const { name, count, doubleCount } = storeToRefs(counter)

// Actions不需要使用storeToRefs
const { increment } = counter
</script>

修改状态的几种方式

  1. 直接修改状态

    counter.count++
    counter.name = 'New Name'
  2. 使用$patch方法(修改多个状态)

    counter.$patch({
      count: counter.count + 1,
      name: 'Updated Name',
      isAdmin: true
    })
  3. 使用$patch方法(函数形式,适合复杂修改)

    counter.$patch((state) => {
      state.count++
      state.items.push({ id: 4, name: 'Item 4' })
    })
  4. 使用Actions(推荐用于复杂逻辑)

    counter.increment()
    counter.incrementBy(5)
    await counter.fetchItems()

重置状态

使用$reset方法可以将Store状态重置为初始值:

counter.$reset()

监听状态变化

使用$subscribe方法可以监听状态变化:

// 监听所有状态变化
counter.$subscribe((mutation, state) => {
  // mutation.type: 'direct' | 'patch object' | 'patch function'
  // mutation.storeId: 'counter'
  // mutation.payload: 传递给$patch的参数
  console.log('State changed:', mutation, state)
})

// 监听特定状态变化
counter.$subscribe((mutation, state) => {
  if (mutation.payload && 'count' in mutation.payload) {
    console.log('Count changed:', state.count)
  }
})

最佳实践与注意事项

  1. Store命名规范

    • Store名称使用驼峰命名法,如useCounterStore
    • 文件名与Store名称对应,如counter.js对应useCounterStore
  2. 状态设计原则

    • 状态应该是扁平化的,避免深层嵌套
    • 每个Store负责一个特定的业务领域
    • 状态应该是可序列化的(可以转换为JSON)
  3. Actions vs 直接修改状态

    • 简单的状态修改可以直接进行
    • 复杂的逻辑、异步操作或需要记录的操作应该放在Actions中
    • Actions可以被其他Actions调用,便于代码复用
  4. Getters的使用

    • 计算派生状态使用Getters
    • Getters可以缓存结果,提高性能
    • 复杂的计算逻辑应该放在Getters中
  5. TypeScript支持

    • Pinia提供了良好的TypeScript支持
    • 可以为状态、Getters和Actions添加类型注解
    • 在VS Code中可以获得完整的类型提示

小结

本节我们学习了Pinia的基础知识,包括:

  • Pinia的安装与配置
  • Store的定义(state、getters、actions)
  • 在组件中使用Store(选项式API和组合式API)
  • 使用storeToRefs保持响应性
  • 修改状态的多种方式
  • 状态重置和监听

Pinia是一个强大而灵活的状态管理库,它的API简洁易用,同时提供了良好的TypeScript支持。通过合理使用Pinia,我们可以更好地管理应用的状态,提高代码的可维护性和可扩展性。

思考与练习

  1. 安装Pinia并配置到Vue应用中。
  2. 创建一个计数器Store,包含count状态、doubleCount getter和increment action。
  3. 在组件中使用该Store,实现计数器功能。
  4. 添加一个异步Action,模拟从API获取数据。
  5. 尝试使用$patch方法修改多个状态。
  6. 使用storeToRefs解构Store状态并保持响应性。
  7. 实现状态重置功能。
« 上一篇 20-advanced-router-features 下一篇 » 22-state-getters