Vue3状态管理Pinia

Vue3状态管理Pinia

Pinia 是一个轻量级的状态管理库,它是 Vue.js 生态系统中的官方推荐替代 Vuex 的工具。Pinia 主要用于 Vue 3+ 中管理应用程序的状态,它比 Vuex 更加现代化、模块化和易于使用。Pinia 的设计理念遵循了 Vue 3 的 Composition API,提供了更加灵活和简洁的 API。

Pinia 的安装
要在 Vue 3 项目中使用 Pinia,首先需要安装它:

npm install pinia

2. Pinia 的基本使用

在 Vue 项目中,首先要创建一个 Pinia 实例,并将其挂载到 Vue 应用中。

// main.js 或 main.ts
import { createApp } from 'vue'
import App from './App.vue'
import { createPinia } from 'pinia'

const app = createApp(App)
app.use(createPinia())  // 安装 Pinia 插件
app.mount('#app')
2.2 创建一个 Store

Pinia 通过 defineStore API 来定义状态管理的 store。每个 store 都有一个独立的状态、getter 和 action。

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

export const useCounterStore = defineStore('counter', {
  state: () => {
    return {
      count: 0
    }
  },
  getters: {
    doubleCount: (state) => state.count * 2
  },
  actions: {
    increment() {
      this.count++
    },
    decrement() {
      this.count--
    }
  }
})
2.3 在组件中使用 Store

在 Vue 组件中使用 store,只需通过 useStore 钩子获取相应的 store 实例。

<template>
  <div>
    <p>Count: {{ counter.count }}</p>
    <p>Double Count: {{ counter.doubleCount }}</p>
    <button @click="counter.increment">Increment</button>
    <button @click="counter.decrement">Decrement</button>
  </div>
</template>

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

export default {
  setup() {
    const counter = useCounterStore()
    return { counter }
  }
}
</script>

3.Pinia 的重要特性


3.1 模块化
Pinia 允许我们将状态管理拆分成多个 store,每个 store 可以包含自己的状态、getter 和 action,这样有助于代码的模块化和可维护性。

3.2 类型支持(TypeScript)
Pinia 对 TypeScript 提供了非常好的支持。你可以通过类型推导来获取 store 的状态、getter 和 action 类型,而不需要手动编写类型定义。

import { defineStore } from 'pinia'

export const useCounterStore = defineStore('counter', {
  state: () => ({
    count: 0
  }),
  getters: {
    doubleCount: (state) => state.count * 2
  },
  actions: {
    increment() {
      this.count++
    }
  }
})

3.3 响应式
Pinia 是基于 Vue 3 的响应式系统构建的,因此它提供了与 Vue 的响应式系统完全一致的行为。状态会自动响应变化,并且会触发组件的更新。

3.4 持久化
Pinia 支持将状态持久化到 localStorage 或 sessionStorage,通过插件机制,可以轻松地实现。

npm install pinia-plugin-persistedstate

在 main.js 中配置插件:

import { createPinia } from 'pinia'
import piniaPersist from 'pinia-plugin-persistedstate'

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

然后在 store 中启用持久化:

export const useCounterStore = defineStore('counter', {
  state: () => ({
    count: 0
  }),
  persist: true
})
3.5 插件机制

Pinia 提供了插件机制,可以轻松地扩展功能。通过 pinia.use(),你可以在应用中全局安装插件,比如持久化、日志等。

4. Pinia 的高级用法

4.1 异步操作

Pinia 允许在 actions 中处理异步操作,例如 API 请求。

export const useCounterStore = defineStore('counter', {
  state: () => ({
    count: 0
  }),
  actions: {
    async fetchCount() {
      const response = await fetch('/api/count')
      const data = await response.json()
      this.count = data.count
    }
  }
})
4.2 Store 的传参

Pinia 支持动态参数传递给 store,例如在创建 store 时,可以传递不同的参数来初始化 store 状态。

export const useUserStore = defineStore('user', {
  state: () => ({
    userName: '',
    userAge: 0
  }),
  actions: {
    setUserData(name, age) {
      this.userName = name
      this.userAge = age
    }
  }
})
4.3 Store 的监听

你可以通过 watch API 来监听 store 中某个状态的变化,或者使用 Pinia 提供的 subscribe 方法来监听状态变动。

import { watch } from 'vue'
import { useCounterStore } from '@/stores/counter'

export default {
  setup() {
    const counter = useCounterStore()
    watch(
      () => counter.count,
      (newCount) => {
        console.log(`Count has changed to: ${newCount}`)
      }
    )
  }
}

5.总结
Pinia 是一个专为 Vue 3 设计的状态管理工具,提供了简洁的 API 和强大的功能,能够轻松应对复杂的状态管理需求。它不仅支持响应式数据、getter 和 actions,还具备类型支持、插件机制、持久化等功能,使得在 Vue 项目中使用它非常方便。如果你已经在使用 Vue 3,那么 Pinia 是一个非常值得推荐的选择。
在 Pinia 中,使用 defineStore 来定义一个 store,通常是通过传递一个对象的方式。然而,Pinia 还允许你以函数形式定义 store,这种方式更加灵活,可以动态创建 store,特别适合需要传递参数或者具有特定逻辑的场景。

  1. 基本概念
    defineStore 本质上是一个函数,可以传递两个参数:

第一个参数是 id,它是 store 的唯一标识符。
第二个参数是一个对象,这个对象包含 store 的配置(state, getters, actions 等)。
而如果你想以函数的方式来定义 store,实际上就是将 defineStore 的第二个参数(配置对象)替换为一个函数,这样可以实现动态生成和计算状态。

  1. 函数形式的 Pinia Store
    函数形式的 Pinia store 主要用于根据某些条件动态创建 store。下面我们通过一些例子来进行详细解释。

2.1 基础示例
在函数形式中,我们传入一个函数,返回一个对象,该对象包含 state, getters 和 actions。这种方式非常适合基于传递的参数或上下文动态生成 store。

import { defineStore } from 'pinia'

export const useCounterStore = defineStore('counter', () => {
  // state
  const count = ref(0)

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

  // actions
  const increment = () => {
    count.value++
  }

  return {
    count,
    doubleCount,
    increment
  }
})

在这个示例中,useCounterStore 是一个基于函数的 store,它返回了一个包含状态 (count)、计算属性 (doubleCount) 和操作方法 (increment) 的对象。

2.2 动态 Store
函数形式的定义方式可以用于创建具有动态状态的 store。例如,基于传递的参数来初始化状态或行为。

import { defineStore } from 'pinia'

export const useUserStore = defineStore('user', (id) => {
  // 动态初始化用户数据
  const user = ref({ id, name: '', email: '' })

  // 定义获取用户信息的 action
  const fetchUser = async () => {
    const response = await fetch(`/api/users/${id}`)
    user.value = await response.json()
  }

  return {
    user,
    fetchUser
  }
})

在这个示例中,我们定义了一个 useUserStore,并使用了 id 参数来动态创建该 store 的状态。每次调用 useUserStore(id) 时,都会根据传入的 id 创建一个新的 store 实例。这种方式非常适合需要针对不同用户加载不同数据的场景。

2.3 与 Vue 组件结合

在 Vue 组件中使用函数形式的 store,我们依然可以使用 useStore() 方法来获取该 store。

<template>
  <div>
    <p>User ID: {{ userStore.user.id }}</p>
    <p>User Name: {{ userStore.user.name }}</p>
    <button @click="userStore.fetchUser">Fetch User</button>
  </div>
</template>

<script>
import { useUserStore } from '@/stores/user'

export default {
  setup() {
    const userStore = useUserStore(1)  // 传递用户 ID
    return { userStore }
  }
}
</script>

在上面的例子中,我们通过 useUserStore(1) 获取了一个 store 实例,并使用它来展示和更新用户数据。

3.动态生成 Store 的优势
(1).基于参数创建 Store:函数形式允许你根据传入的参数动态创建 store,非常适合具有个性化需求的状态管理。例如,针对不同的用户、不同的主题或配置加载不同的状态。

(2).更加灵活:由于 defineStore 的第二个参数是一个函数,它能够灵活地根据上下文或配置返回不同的 store 配置,使得 store 更具复用性和可配置性。

(3).减少冗余:可以通过函数内的逻辑来减少重复的代码逻辑,例如根据不同的 id 加载不同的数据,或根据环境变量加载不同的状态。

4.完整的示例:函数形式的 Pinia Store
假设我们有一个应用需要管理多个用户的状态,每个用户都有不同的配置,我们可以使用函数形式来动态创建每个用户的 store。

import { defineStore } from 'pinia'

// 动态创建每个用户的 store
export const useUserStore = defineStore('user', (userId) => {
  const user = ref({
    id: userId,
    name: '',
    email: ''
  })
  const loading = ref(false)

  // 异步加载用户数据
  const fetchUserData = async () => {
    loading.value = true
    try {
      const response = await fetch(`/api/users/${userId}`)
      const data = await response.json()
      user.value = data
    } catch (error) {
      console.error('Failed to fetch user data', error)
    } finally {
      loading.value = false
    }
  }

  return {
    user,
    loading,
    fetchUserData
  }
})
在 Vue 组件中使用:
<template>
  <div v-if="!userStore.loading">
    <p>Name: {{ userStore.user.name }}</p>
    <p>Email: {{ userStore.user.email }}</p>
    <button @click="userStore.fetchUserData">Fetch User</button>
  </div>
  <div v-else>
    <p>Loading...</p>
  </div>
</template>

<script>
import { useUserStore } from '@/stores/user'

export default {
  setup() {
    // 动态获取不同用户的 store
    const userStore = useUserStore(123)  // 传递特定用户 ID
    userStore.fetchUserData()  // 加载用户数据

    return { userStore }
  }
}
</script>

5.总结
使用 Pinia 的函数形式来定义 store 是一种非常灵活的方式,它允许你根据传入的参数动态创建 store。这种方法特别适合那些需要动态初始化或具有动态行为的状态管理场景。

优势:

动态参数:可以根据不同的参数初始化 store 的状态和行为。
灵活性:通过函数的逻辑可以减少重复代码,使得 store 更加灵活和可配置。
应用场景:动态加载用户数据、根据配置动态创建 store、具有个性化配置的状态管理等。

通过这种方式,你可以极大地提高应用的可扩展性和可维护性。
在 Pinia 中,storeToRefs 是一个非常实用的工具函数,用于将 store 中的响应式状态解构成普通的 ref 对象。它可以帮助你在 Vue 组件中更加方便地使用 Pinia store 的状态,避免直接对 store 对象进行解构而导致响应式失效的问题。