05-Pinia状态管理

Pinia 状态管理

1. 什么是 Pinia

Pinia 是 Vue 的新一代状态管理库,由 Vue 团队开发。它提供了更简洁的 API、更好的 TypeScript 支持和开发工具集成。

Pinia 的优势

  • 完整的 TypeScript 支持
  • 无需 mutations,只有 state、getters、actions
  • 支持 Vue 2 和 Vue 3
  • 自动代码分割
  • 插件系统支持扩展
  • DevTools 集成

2. 安装与配置

安装

npm install pinia

创建 Store

// stores/index.js
import { createPinia } from 'pinia'

const pinia = createPinia()

export default pinia

在应用中使用

// main.js
import { createApp } from 'vue'
import App from './App.vue'
import pinia from './stores'

const app = createApp(App)
app.use(pinia)
app.mount('#app')

3. 定义 Store

Option Store 风格

// stores/counter.js
import { defineStore } from 'pinia'

export const useCounterStore = defineStore('counter', {
// State
state: () => {
return {
count: 0,
name: '计数器'
}
},

// Getters
getters: {
doubleCount: (state) => state.count * 2,

// 接受参数的 getter
countPlus: (state) => {
return (num) => state.count + num
},

// 使用其他 getter
doubleCountPlusOne() {
return this.doubleCount + 1
}
},

// Actions
actions: {
increment() {
this.count++
},

decrement() {
this.count--
},

// 异步操作
async fetchCount() {
const response = await fetch('/api/count')
const data = await response.json()
this.count = data.count
},

// 接受参数
incrementBy(amount) {
this.count += amount
}
}
})

Setup Store 风格

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

export const useCounterStore = defineStore('counter', () => {
// State
const count = ref(0)
const name = ref('计数器')

// Getters
const doubleCount = computed(() => count.value * 2)

// Actions
function increment() {
count.value++
}

function decrement() {
count.value--
}

async function fetchCount() {
const response = await fetch('/api/count')
const data = await response.json()
count.value = data.count
}

// 返回需要暴露的内容
return {
count,
name,
doubleCount,
increment,
decrement,
fetchCount
}
})

4. 使用 Store

在组件中使用

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

const counterStore = useCounterStore()

// 访问 state
console.log(counterStore.count)

// 访问 getter
console.log(counterStore.doubleCount)

// 调用 action
counterStore.increment()
counterStore.incrementBy(5)
</script>

<template>
<div>
<p>计数: {{ counterStore.count }}</p>
<p>双倍: {{ counterStore.doubleCount }}</p>
<button @click="counterStore.increment">增加</button>
<button @click="counterStore.decrement">减少</button>
</div>
</template>

使用 storeToRefs 保持响应性

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

const counterStore = useCounterStore()

// 解构时保持响应性
const { count, doubleCount } = storeToRefs(counterStore)

// actions 不需要 storeToRefs
const { increment, decrement } = counterStore
</script>

<template>
<div>
<p>计数: {{ count }}</p>
<p>双倍: {{ doubleCount }}</p>
<button @click="increment">增加</button>
</div>
</template>

5. State 操作

读取 State

const store = useCounterStore()

// 直接访问
console.log(store.count)

// 解构(需要 storeToRefs)
const { count } = storeToRefs(store)

修改 State

const store = useCounterStore()

// 方式一:直接修改
store.count++

// 方式二:通过 action
store.increment()

// 方式三:$patch 对象形式
store.$patch({
count: 10,
name: '新名称'
})

// 方式四:$patch 函数形式(适合数组操作)
store.$patch((state) => {
state.count++
state.name = '修改后的名称'
state.items.push({ id: 1, name: '新项' })
})

// 方式五:替换整个 state
store.$state = {
count: 0,
name: '重置'
}

重置 State

// 重置到初始值
store.$reset()

6. Getters

基础 Getter

export const useTodoStore = defineStore('todo', {
  state: () => ({
    todos: [
      { id: 1, text: '学习 Vue', done: false },
      { id: 2, text: '学习 Pinia', done: true }
    ]
  }),

getters: {
// 简单 getter
finishedTodos: (state) => {
return state.todos.filter(todo => todo.done)
},

// 计算属性
unfinishedCount: (state) => {
return state.todos.filter(todo => !todo.done).length
},

// 访问其他 getter
summary() {
return ${this.finishedTodos.length} 已完成,${this.unfinishedCount} 未完成
}
}
})

接受参数的 Getter

getters: {
  // 返回一个函数
  getTodoById: (state) => {
    return (id) => state.todos.find(todo => todo.id === id)
  }
}

// 使用
const todo = store.getTodoById(1)

7. Actions

同步 Action

actions: {
  addTodo(text) {
    this.todos.push({
      id: Date.now(),
      text,
      done: false
    })
  },

toggleTodo(id) {
const todo = this.todos.find(t => t.id === id)
if (todo) {
todo.done = !todo.done
}
},

removeTodo(id) {
this.todos = this.todos.filter(t => t.id !== id)
},

// 调用其他 action
resetTodos() {
this.todos = []
this.addTodo('默认任务')
}
}

异步 Action

actions: {
  async fetchTodos() {
    try {
      const response = await fetch('/api/todos')
      const data = await response.json()
      this.todos = data
    } catch (error) {
      console.error('获取任务失败:', error)
    }
  },

async addTodoAsync(text) {
try {
const response = await fetch('/api/todos', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text })
})
const newTodo = await response.json()
this.todos.push(newTodo)
} catch (error) {
console.error('添加任务失败:', error)
}
}
}

8. Store 订阅

订阅 State 变化

const store = useCounterStore()

// 监听 state 变化
store.$subscribe((mutation, state) => {
console.log('变化类型:', mutation.type) // direct, patch object, patch function
console.log('store id:', mutation.storeId)
console.log('当前 state:', state)

// 可用于持久化到 localStorage
localStorage.setItem('counter', JSON.stringify(state))
}, { detached: true })

订阅 Action 调用

const store = useCounterStore()

store.$onAction(({ name, store, args, after, onError }) => {
console.log(Action &quot;${name}&quot; 被调用,参数:, args)

after((result) => {
console.log(Action &quot;${name}&quot; 完成,结果:, result)
})

onError((error) => {
console.error(Action &quot;${name}&quot; 出错:, error)
})
})

9. Store 组合

// stores/user.js
export const useUserStore = defineStore('user', {
  state: () => ({
    token: null,
    userInfo: null
  }),
  actions: {
    async login(credentials) {
      // ...
    }
  }
})

// stores/cart.js
export const useCartStore = defineStore('cart', {
state: () => ({
items: []
}),

getters: {
// 使用其他 store
totalPrice: (state) => {
const userStore = useUserStore()

// 根据用户等级计算折扣
if (userStore.userInfo?.vip) {
return state.items.reduce((sum, item) => sum + item.price * 0.8, 0)
}

return state.items.reduce((sum, item) => sum + item.price, 0)
}
}
})

10. 持久化存储

使用 pinia-plugin-persistedstate

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

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

// stores/counter.js
export const useCounterStore = defineStore('counter', {
  state: () => ({
    count: 0
  }),
  
  // 开启持久化
  persist: true
  
  // 或者自定义配置
  // persist: {
  //   key: 'my-counter',
  //   storage: localStorage,
  //   paths: ['count']
  // }
})

11. 完整示例:Todo 应用

// stores/todo.js
import { defineStore } from 'pinia'

export const useTodoStore = defineStore('todo', {
state: () => ({
todos: [],
filter: 'all' // all, active, completed
}),

getters: {
filteredTodos() {
switch (this.filter) {
case 'active':
return this.todos.filter(t => !t.done)
case 'completed':
return this.todos.filter(t => t.done)
default:
return this.todos
}
},

activeCount: (state) => {
return state.todos.filter(t => !t.done).length
},

completedCount: (state) => {
return state.todos.filter(t => t.done).length
}
},

actions: {
addTodo(text) {
this.todos.push({
id: Date.now(),
text,
done: false,
createdAt: new Date()
})
},

toggleTodo(id) {
const todo = this.todos.find(t => t.id === id)
if (todo) todo.done = !todo.done
},

removeTodo(id) {
this.todos = this.todos.filter(t => t.id !== id)
},

clearCompleted() {
this.todos = this.todos.filter(t => !t.done)
},

setFilter(filter) {
this.filter = filter
},

async loadTodos() {
try {
const response = await fetch('/api/todos')
this.todos = await response.json()
} catch (error) {
console.error('加载失败:', error)
}
},

async saveTodos() {
try {
await fetch('/api/todos', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(this.todos)
})
} catch (error) {
console.error('保存失败:', error)
}
}
},

persist: true
})

<!-- TodoApp.vue -->
<script setup>
import { ref } from 'vue'
import { storeToRefs } from 'pinia'
import { useTodoStore } from '@/stores/todo'

const todoStore = useTodoStore()
const { filteredTodos, activeCount, completedCount } = storeToRefs(todoStore)
const newTodo = ref('')

function addTodo() {
  if (newTodo.value.trim()) {
    todoStore.addTodo(newTodo.value.trim())
    newTodo.value = ''
  }
}
</script>

<template>
  <div class="todo-app">
    <h1>待办事项</h1>
    
    <!-- 添加 -->
    <form @submit.prevent="addTodo">
      <input v-model="newTodo" placeholder="添加新任务..." />
      <button type="submit">添加</button>
    </form>
    
    <!-- 筛选 -->
    <div class="filters">
      <button @click="todoStore.setFilter('all')">全部</button>
      <button @click="todoStore.setFilter('active')">进行中</button>
      <button @click="todoStore.setFilter('completed')">已完成</button>
    </div>
    
    <!-- 列表 -->
    <ul>
      <li v-for="todo in filteredTodos" :key="todo.id">
        <input type="checkbox" :checked="todo.done" @change="todoStore.toggleTodo(todo.id)" />
        <span :class="{ done: todo.done }">{{ todo.text }}</span>
        <button @click="todoStore.removeTodo(todo.id)">删除</button>
      </li>
    </ul>
    
    <!-- 统计 -->
    <footer>
      <span>{{ activeCount }} 项未完成</span>
      <button @click="todoStore.clearCompleted" v-show="completedCount">清除已完成</button>
    </footer>
  </div>
</template>

小结

Pinia 是 Vue 3 推荐的状态管理库:

  • 定义 Store:使用 defineStore,支持 Option 和 Setup 两种风格
  • State:存储应用状态,支持响应式修改
  • Getters:计算派生状态,类似 computed
  • Actions:修改 state 的方法,支持异步操作
  • 订阅:监听 state 和 action 变化
  • 组合:store 之间可以相互引用
  • 持久化:通过插件轻松实现数据持久化
posted @ 2026-06-16 13:06  心梦EGO  阅读(5)  评论(0)    收藏  举报