vue2(Pinia)

2 阅读4分钟

useAppStore

import { useAppStore } from '@/stores/AppSetting';
const appStore = useAppStore();

useAppStore是一个Pinia定义的store函数,为什么我们通常写成:

const appStore = useAppStore();

而不是每次都调用useAppStore():

  1. useAppStore() 是一个“获取入口函数

useAppStore() 本身并不是一个全局对象,而是一个返回 store 实例的函数

当你调用它时:

  • 它会去Pinia的“全局容器”里查找(或创建)对应的store实例
  • 然后返回这个store对象(带有state,getters,actions)

所以const appStore = useAppStore();就是在当前作用域中拿到这个store实例

  1. 为了防止反复调用而浪费的性能

虽然Pinia会缓存返回的实例(不会创建多个store),但:

  • 每次调用都需要重新执行函数;
  • 可读性和效率都不好;
  • 一旦 store 需要注入依赖(比如不同 pinia 实例),可能还会出问题。

Pinia

Vue3的官方状态库,是Vuex的继任者

Pinia是什么

Pinia 是一个用来在 Vue 应用中**集中管理状态(state)**的库。

也就是说,Pinia负责存放那些:

  • 不适合放在单个组件里的数据
  • 需要在多个组件之间共享的数据
  • 或者需要全局保存的设置、登录状态、主题等

简单来说:组件自己的数据放在data()或ref()里,而多个组件要共享的数据放在Pinia里

类似Java中的全局变量:

public class GlobalState {
    public static boolean sidebarOpened = true;
}

在程序运行期间,任何类都可以通过 GlobalState.sidebarOpened 访问或修改这个变量。

Pinia 的 store 其实也类似:

import { useAppStore } from '@/stores/AppSetting'
const appStore = useAppStore()
appStore.sidebar.opened = true

无论你在 Vue 的哪个组件中调用 useAppStore(),拿到的都是同一个 store 实例
这就像 Java 里所有类访问的都是同一个 GlobalState

不同点是前端应用不像Java服务那样“一直运行”,浏览器页面一旦刷新,JavaScript代码就会:

  • 全部重新加载
  • 所有内存(包括Pinia的store)都会被清空
  • 重新执行Vue应用的初始逻辑

因此,Pinia 中的数据虽然在页面运行期间是全局共享的,但刷新后就会丢失
除非你手动把数据持久化到 localStorage、sessionStorage 或 IndexedDB

为什么要用Pinia?

在一个小项目中,每个组件都有自己的状态还没有问题

但在大项目里,会出现:

  • A组件修改数据 -> B组件跟着更新
  • C组件需要知道登录状态
  • D组件要控制全局主题颜色

如果这些数据分散在组件里,会很乱。这时候就需要一个全局数据中心,让所有组件都能访问和修改。

Pinia就是这个中心:

ComponentA 改变状态 -> Pinia Store 保存状态 -> ComponentB 自动更新

Pinia的核心概念

Pinia有三个主要部分:

名称作用类比
state存储数据Vue 的 data()
getters计算属性Vue 的 computed()
actions修改数据的方法Vue 的 methods()

示例:

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

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

函数体:

  • defindStore: Pinia的核心函数,用来定义一个全局store
  • counter: 是Store的唯一ID(就像变量名),在调试或插件中用来区分不同的store
  • useCounterStore: 是导出的函数名,组件中调用它就能拿到store实例

函数内部:

state

state: () => ({
    count: 0,
})
  • 相当于Vue组件的data()
  • 用来定义全局的响应式状态
  • count初始值为0
counter.count // 访问状态
counter.count++ // 修改状态

Pinia 会自动把 state 包装成响应式的(用 Vue 的 reactive() 实现)。

getter

getters: {
    double: (state) => state.count * 2,
}
  • 类似vue的computed计算属性
  • 不直接修改状态,而是根据state计算出一个衍生值
  • 当count改变时,double会自动更新

actions

  actions: {
    increment() {
      this.count++;
    },
  }
  • actions:就像vue组件里的methods
  • 可以执行逻辑(同步或异步)并修改state
  • 在这里,this就指向当前store实例
<script setup>
import { useCounterStore } from '@/stores/counter';

const counter = useCounterStore();
</script>

<template>
  <div>
    <p>当前计数:{{ counter.count }}</p>
    <p>两倍值:{{ counter.double }}</p>
    <button @click="counter.increment">+1</button>
  </div>
</template>

组合式写法

如果你更习惯 Vue 3 的 ref / reactive 写法,也可以这么写


import { defineStore } from 'pinia'
import { ref, computed } from 'vue'

export const useCounterStore = defineStore('counter', () => {
  // state:定义响应式变量
  const count = ref(0)

  // getters:定义计算属性
  const double = computed(() => count.value * 2)

  // actions:定义操作函数
  function increment() {
    count.value++
  }

  // 返回要暴露给外部使用的属性和方法
  return { count, double, increment }
})