Skip to content

第一章:重新理解 Pinia 的本质

在 Vue 生态中,状态管理一直是重要的一环。从 Vuex 到 Pinia,Vue 官方的状态管理解决方案发生了显著的变化。但很多开发者只是停留在如何使用 Pinia 的层面,很少深入理解其设计思想和实现原理。

在本章中,我们将重新审视 Pinia 的本质,理解它为什么不是"Vuex 5",而是 Vue3 响应式系统的"第一公民"。

Pinia 不是 Vuex 5,它是 Vue3 响应式系统的"第一公民"

很多人初学 Pinia 时,会把它当作 Vuex 的升级版,认为它只是 Vuex 5 的另一种实现。但这种理解是错误的。Pinia 的设计哲学与 Vuex 有着根本的不同:

typescript
// Vuex 的方式 - 通过 mutations 修改状态
const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment(state) {
      state.count++
    }
  },
  actions: {
    incrementAsync({ commit }) {
      setTimeout(() => {
        commit('increment')
      }, 1000)
    }
  }
})

// Pinia 的方式 - 直接修改状态
import { defineStore } from 'pinia'

export const useCounterStore = defineStore('counter', () => {
  const count = ref(0)
  
  function increment() {
    count.value++
  }
  
  async function incrementAsync() {
    setTimeout(() => {
      increment()
    }, 1000)
  }
  
  return { count, increment, incrementAsync }
})

从上面的对比可以看出,Pinia 抛弃了 Vuex 中复杂的概念,如 Mutations、Modules 等,而是直接利用 Vue3 的响应式系统。这种方式更加直观,也更容易理解。

基于 reactiverefcomputed,无 Mutation/Module 的复杂抽象

Pinia 的核心就是 Vue3 的响应式 API。它没有重新发明轮子,而是直接利用了 Vue3 提供的 reactiverefcomputed

typescript
// 简化版的 Pinia 实现原理
import { reactive, ref, computed } from 'vue'

function defineStore(id, setup) {
  // 存储实例
  const stores = new Map()
  
  return function useStore() {
    // 如果已经创建过,直接返回
    if (stores.has(id)) {
      return stores.get(id)
    }
    
    // 执行 setup 函数获取初始状态
    const store = setup()
    
    // 使用 reactive 包装整个 store
    const reactiveStore = reactive(store)
    
    // 保存实例
    stores.set(id, reactiveStore)
    
    return reactiveStore
  }
}

// 使用示例
const useUserStore = defineStore('user', () => {
  const user = reactive({
    name: '',
    age: 0
  })
  
  const isAdmin = computed(() => user.role === 'admin')
  
  const updateName = (name) => {
    user.name = name
  }
  
  return { user, isAdmin, updateName }
})

这种设计使得 Pinia 更加轻量,也更容易与 Vue3 的其他特性集成。

Composition API + 响应式:状态即 reactive 对象,计算即 computed

Pinia 与 Vue3 的 Composition API 完美融合,使得状态管理变得更加自然:

typescript
// 在组件中使用
import { useCounterStore } from '@/stores/counter'

export default {
  setup() {
    // 直接获取 store 实例
    const counter = useCounterStore()
    
    // 可以直接解构,而不会失去响应性
    const { count, increment } = storeToRefs(counter)
    
    return {
      count,
      increment,
      // 可以直接使用 store 上的方法
      counter
    }
  }
}

这种使用方式与 Composition API 的理念完全一致,开发者不需要学习额外的概念,只需要掌握 Vue3 的响应式系统即可。

TypeScript 优先:类型自动推导,无需冗余声明

Pinia 在设计之初就考虑了 TypeScript 的支持,提供了出色的类型推导能力:

typescript
import { defineStore } from 'pinia'

interface User {
  id: number
  name: string
  email: string
}

export const useUserStore = defineStore('user', () => {
  const user = ref<User | null>(null)
  const isLoggedIn = computed(() => !!user.value)
  
  async function login(credentials: { email: string; password: string }) {
    // 登录逻辑
    const userData = await api.login(credentials)
    user.value = userData
    return userData
  }
  
  function logout() {
    user.value = null
  }
  
  return {
    user,
    isLoggedIn,
    login,
    logout
  }
})

// 在组件中使用时,TypeScript 会自动推导出所有类型
const userStore = useUserStore()
// userStore.user 类型为 User | null
// userStore.login 参数类型为 { email: string; password: string }
// userStore.isLoggedIn 类型为 ComputedRef<boolean>

这种自动类型推导大大减少了开发者需要编写的类型声明代码,同时保证了类型安全。

模块化设计:defineStore(id, () => {...}) 支持逻辑拆分

Pinia 通过 defineStore 函数实现模块化设计,每个 store 都是独立的:

typescript
// stores/user.js
export const useUserStore = defineStore('user', () => {
  const user = ref(null)
  
  async function login(credentials) {
    // 登录逻辑
  }
  
  return { user, login }
})

// stores/cart.js
export const useCartStore = defineStore('cart', () => {
  const items = ref([])
  
  function addToCart(product) {
    items.value.push(product)
  }
  
  const total = computed(() => {
    return items.value.reduce((sum, item) => sum + item.price, 0)
  })
  
  return { items, addToCart, total }
})

每个 store 都有唯一的 ID,可以在应用的任何地方通过 useStore() 函数获取其实例。

轻量核心:无 Mutation、无 Action Payload、无 Module 嵌套

Pinia 的设计哲学是"少即是多"。它移除了 Vuex 中很多复杂的概念:

  1. 无 Mutations - 直接修改状态,无需通过 commit
  2. 无 Action Payload - Actions 就是普通函数,参数传递更自然
  3. 无 Module 嵌套 - 通过组合多个 store 实现模块化
typescript
// Vuex 风格
store.commit('user/setRole', 'admin')
store.dispatch('cart/addToCart', { id: 1, name: 'Product' })

// Pinia 风格
userStore.setRole('admin')
cartStore.addToCart({ id: 1, name: 'Product' })

这种简化不仅减少了学习成本,也减少了代码量,使得状态管理更加直观。

小结

在本章中,我们深入探讨了 Pinia 的本质和设计哲学:

  1. Pinia 是 Vue3 响应式系统的"第一公民",而不是 Vuex 的简单升级
  2. 它直接利用 Vue3 的响应式 API,无需重新发明轮子
  3. 与 Composition API 完美融合,使用方式更加自然
  4. 提供出色的 TypeScript 支持,类型自动推导
  5. 通过模块化设计支持逻辑拆分
  6. 移除了 Vuex 中复杂的概念,使 API 更加简洁

在下一章中,我们将深入探讨 Pinia 的响应式状态实现机制,包括 state 的 reactive 实现、$patch 批量更新原理等内容。


思考题

  1. 你在项目中使用 Pinia 时,有没有遇到过与 Vuex 使用习惯冲突的情况?
  2. 你觉得 Pinia 移除 Mutations 的设计是好是坏?为什么?