当前位置: 首页 > news >正文

前端面经-VUE3篇(四)--pinia篇-基本使用、store、state、getter、action、插件

一、基本使用

1、什么是 Pinia?

Pinia 是 Vue.js 的官方状态管理库,是 Vuex 的“升级版”。它专为 Vue 3 和 Composition API 设计,用于管理多个组件之间共享的数据(也叫“全局状态”)。

2、为什么需要状态管理库?

在 Vue 中,多个组件如果需要共享数据(例如登录用户信息、购物车内容、权限配置等),仅使用 props 和 emits 会变得非常复杂。这时就需要一个“中心仓库”来统一管理这些数据 —— 这就是 Pinia 的作用。

举个例子

假设你有一个购物车组件、一个商品列表组件和一个订单组件,都需要访问或修改购物车的内容。这种“共享数据”就是 Pinia 的用武之地。

在没有 Pinia 的情况下你可能需要这样做:

  • 商品列表通过 emit 告诉父组件加入购物车

  • 父组件再传递数据给购物车组件

  • 数据很容易混乱且难以追踪

用 Pinia 后,这些组件可以直接访问全局共享的购物车数据,逻辑清晰,易于维护。

3、 基本使用

1. 配置 Pinia 到 Vue 应用在 main.js 或 main.ts 中引入并注册:
// main.js
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'const app = createApp(App)
const pinia = createPinia()app.use(pinia)
app.mount('#app')2. 创建一个 Store(状态模块)
你可以把每个业务逻辑写在一个独立的 store 文件中,例如:
// stores/counter.js 或 counter.ts
import { defineStore } from 'pinia'export const useCounterStore = defineStore('counter', {state: () => ({count: 0}),getters: {doubleCount: (state) => state.count * 2},actions: {increment() {this.count++}}
})
state: 用来定义数据getters: 类似于计算属性actions: 用来修改数据(支持异步)
3. 在组件中使用 Store
<script setup>
import { useCounterStore } from '@/stores/counter'const counter = useCounterStore()function addOne() {counter.increment()
}
</script><template><div>当前计数:{{ counter.count }}</div><div>双倍计数:{{ counter.doubleCount }}</div><button @click="addOne">+1</button>
</template>

二、store

1、什么是 Pinia 的 Store?

在 Pinia 中,store 是用来集中管理状态、逻辑和数据的地方。它就像是一个“全局的数据容器”,供多个 Vue 组件共享和操作。

每一个 store 都是独立的模块,可以包含:

  • state(状态数据)

  • getters(计算属性)

  • actions(行为函数)

 Store 是用 defineStore() 定义的,它的第一个参数要求是一个独一无二的名字。这个名字 ,也被用作 id ,是必须传入的, Pinia 将用它来连接 store 和 devtools。为了养成习惯性的用法,将返回的函数命名为 use... 是一个符合组合式函数风格的约定。

import { defineStore } from 'pinia'// 你可以任意命名 `defineStore()` 的返回值,但最好使用 store 的名字,同时以 `use` 开头且以 `Store` 结尾。
// (比如 `useUserStore`,`useCartStore`,`useProductStore`)
// 第一个参数是你的应用中 Store 的唯一 ID。
export const useAlertsStore = defineStore('alerts', {// 其他配置...
})

defineStore() 的第二个参数可接受两类值:Setup 函数或 Option 对象。

1、Option Store​

与 Vue 的选项式 API 类似,我们也可以传入一个带有 stateactions 与 getters 属性的 Option 对象

export const useCounterStore = defineStore('counter', {state: () => ({ count: 0, name: 'Eduardo' }),getters: {doubleCount: (state) => state.count * 2,},actions: {increment() {this.count++},},
})
你可以认为 state 是 store 的数据 (data),getters 是 store 的计算属性 (computed),而 actions 则是方法 (methods)。

2、Setup Store​

也存在另一种定义 store 的可用语法。与 Vue 组合式 API 的 setup 函数 相似,我们可以传入一个函数,该函数定义了一些响应式属性和方法,并且返回一个带有我们想暴露出去的属性和方法的对象。

export const useCounterStore = defineStore('counter', () => {const count = ref(0)const doubleCount = computed(() => count.value * 2)function increment() {count.value++}return { count, doubleCount, increment }
})

在 Setup Store 中:

  • ref() 就是 state 属性
  • computed() 就是 getters
  • function() 就是 actions

注意,要让 pinia 正确识别 state,你必须在 setup store 中返回 state 的所有属性。这意味着,你不能在 store 中使用私有属性。不完整返回会影响 SSR ,开发工具和其他插件的正常运行。 

2、使用 Store​

虽然我们前面定义了一个 store,但在我们使用 <script setup> 调用 useStore()(或者使用 setup() 函数,像所有的组件那样) 之前,store 实例是不会被创建的:

<script setup>
import { useCounterStore } from '@/stores/counter'
// 可以在组件中的任意位置访问 `store` 变量 ✨
const store = useCounterStore()
</script> 

请注意,store 是一个用 reactive 包装的对象,这意味着不需要在 getters 后面写 .value。就像 setup 中的 props 一样,我们不能对它进行解构

<script setup>
import { useCounterStore } from '@/stores/counter'
import { computed } from 'vue'const store = useCounterStore()
// ❌ 这将不起作用,因为它破坏了响应性
// 这就和直接解构 `props` 一样
const { name, doubleCount } = store
name // 将始终是 "Eduardo" //
doubleCount // 将始终是 0 //
setTimeout(() => {store.increment()
}, 1000)
// ✅ 这样写是响应式的
// 💡 当然你也可以直接使用 `store.doubleCount`
const doubleValue = computed(() => store.doubleCount)
</script>

 3、如何从 Pinia 的 store 中 解构变量 的同时 保持响应式

 在 Vue 中,如果你这样写:const { name, doubleCount } = useCounterStore()

那么 namedoubleCount失去响应性,也就是说它们变成了普通变量,不再随着 store 的状态变化自动更新页面。 

 使用 storeToRefs():

import { storeToRefs } from 'pinia'const store = useCounterStore()
const { name, doubleCount } = storeToRefs(store)

 storeToRefs() 会把 store 中的所有响应式属性(stategetters转换成 ref 对象,从而保留响应性。

const { increment } = store

这里可以直接解构 action,因为:

  • actions 本身就是函数

  • 它们是绑定过 this 的,不需要 ref 包装

  • 所以解构时不会失效,直接拿来调用即可

4、总结

属性类型是否需要 storeToRefs()是否保持响应性是否可以解构
state 数据✅ 需要✅(通过 storeToRefs)
getters✅ 需要✅(通过 storeToRefs)
actions❌ 不需要✅(始终)✅(直接解构)

三、state 

 在 Pinia 中,state 就是你用来存储全局数据的地方。它相当于组件中的 data(),但它可以被多个组件共享。

 例如:

state: () => ({count: 0,user: {name: 'Xia',loggedIn: false}
})

 这段代码定义了两个状态变量:countuser,你可以在任何组件中使用和修改它们。

1、state 的三个关键点

1. 必须是一个函数(返回对象)

这是为了确保每个 store 实例有自己的状态(尤其是在 SSR 环境中)。

state: () => ({count: 0
})

2. 是响应式的

Pinia 内部使用 Vue 的响应式系统(reactivity API)来管理状态。这意味着当你修改 state 中的数据,使用它的组件会自动更新。

3. 可以直接读写

与 Vuex 不同,Pinia 的 state 变量可以直接修改,不需要通过 mutation。例如:

store.count++  // 这是合法的
store.user.name = 'Yang' // 也可以这样直接改

2、 state高级用法

1. 重置 state($reset() 方法)

作用

  • state 恢复为创建时定义的初始值。

  • 适合用于退出登录清空表单重置计数器等场景。

使用方式

选项式 API 可直接使用 store.$reset()

const store = useStore()
store.$reset()
这个方法会自动调用最初定义的 state() 函数,用返回的对象替换当前的 state。

组合式 API 需要手动实现 $reset() 方法。 

 如果你使用的是 组合式 API(即 defineStore('id', () => {...}) 格式),需要自己手动写一个 $reset() 方法:

export const useCounterStore = defineStore('counter', () => {const count = ref(0)function $reset() {count.value = 0}return { count, $reset }
})

2、$patch() — 批量或复杂修改 state

作用

  • 用于一次性更新多个 state 属性

  • 避免多次赋值产生性能问题,也方便调试(devtools 会合并记录)。

  • 支持复杂的数组/对象操作。

用法

对象方式(简洁)
store.$patch({count: store.count + 1,name: 'DIO'
})函数方式(适合复杂修改)
store.$patch((state) => {state.items.push({ id: 1, name: 'shoes' })
})

 $state — 替换整个 state(实际上是 patch)

作用

  • 用于初始化或从服务器、localStorage 恢复 state。

  • 虽然看起来是“覆盖”,但实际上是调用了 $patch()

store.$state = { count: 24, name: 'Zhang' }

 3、mapState() — 把 state 映射为只读计算属性

作用

  • 用于在选项式 API 的组件中使用 Pinia 的 state。

  • store.count 变成 this.count,方便模板访问。

  • 本质上是一个只读 computed()

computed: {...mapState(useCounterStore, ['count'])
}

 4、mapWritableState() — 把 state 映射为可写计算属性

作用

  • mapState() 类似,但支持修改值

  • 适合表单绑定、v-model 等双向数据绑定场景。

computed: {...mapWritableState(useCounterStore, ['count'])
}
✅ 你可以在模板中用 v-model="count" 实现响应式输入。

5、$subscribe() — 监听 state 的变化

作用

  • 监听 store 的 state 改变,并触发回调。

  • 常用于持久化(localStorage)发送日志自动保存等场景。

  • 与 Vue 的 watch() 不同:$subscribe()patch 触发一次,不是属性粒度触发

store.$subscribe((mutation, state) => {localStorage.setItem('cart', JSON.stringify(state))
})

 加上 { detached: true },让监听器在组件卸载后仍然有效(适合全局监听)。

6、watch(pinia.state) — 监听整个应用状态(全局级别)

✅ 作用

  • 用于在应用级别跟踪所有 store 的状态。

  • 常用于初始化持久化系统跨模块状态变化处理

watch(pinia.state,(state) => {localStorage.setItem('piniaState', JSON.stringify(state))},{ deep: true }
)

小知识: 

初始化持久化系统”是指在 Vue 应用中,通过 Pinia 将全局状态(state)保存到浏览器的本地存储(如 localStorage),并在应用加载时恢复之前保存的状态,从而实现“断电重连”、“页面刷新不丢数据”等功能。

 “跨模块状态变化处理”指的是:监听或响应多个 Pinia store 之间的状态变动,从而实现它们之间的协调、联动或同步逻辑。

为什么需要跨模块状态处理?

在实际开发中,你通常会为不同功能创建多个 store(模块化):

Store 模块功能
userStore管理登录用户信息
cartStore管理购物车内容
orderStore管理订单信息
uiStore控制 UI 状态(弹窗、loading 等)

但这些模块的状态往往不是孤立的

  • 用户退出登录时,需要清空购物车、重置订单模块

  • 添加商品到购物车后,UI 弹窗需要自动关闭

  • 切换语言后,需要让其他模块响应更新

四、Getter 

1、什么是 Getter?

在 Pinia 中,getter 就是计算属性(computed),用于从 state 中派生出一些数据,例如格式化、计算总和、过滤等。

它的作用就像:

  • Vue 组件中的 computed

  • Vuex 中的 getter

2、Getter 的作用

主要功能举例说明
派生出新值例如 items.lengthcount * 2是否登录
基于 state 但不会直接修改 state保证“只读逻辑”
提高代码可读性通过 getter 抽象逻辑,例如 isAdmin
性能优化只有依赖的值变化时才重新计算(和 computed 一样)

 Getter 的定义方式

在选项式 API中(推荐初学者)
export const useCounterStore = defineStore('counter', {state: () => ({count: 2}),getters: {doubleCount: (state) => state.count * 2,isEven: (state) => state.count % 2 === 0}
})在组合式 API中(进阶)
export const useCounterStore = defineStore('counter', () => {const count = ref(2)const doubleCount = computed(() => count.value * 2)return { count, doubleCount }
})

 Getter 的使用方式

在组件中直接访问(不需要调用)
<script setup>
import { useCounterStore } from '@/stores/counter'
const counter = useCounterStore()
</script><template><p>原始:{{ counter.count }}</p><p>双倍:{{ counter.doubleCount }}</p>
</template>
✅ 注意:getter 就像计算属性,是自动响应式的,不需要加括号。✍️ Getter 可以访问其他 state、getter
getters: {isEven: (state) => state.count % 2 === 0,label: (state, getters) => getters.isEven ? '偶数' : '奇数'
}

3、Getter 是只读的

你不能直接修改 getter 的返回值:

 store.doubleCount = 10 // ❌ 不允许,会报错

 如果你需要修改值,请通过 action 或直接改 state。

 1. 访问其他 Getter(组合 Getter)

 当你想让一个 getter 基于另一个 getter 的结果继续计算,可以直接通过 this 访问其他 getter。

export const useCounterStore = defineStore('counter', {state: () => ({ count: 0 }),getters: {doubleCount(state) {return state.count * 2},doubleCountPlusOne(): number {return this.doubleCount + 1}}
})
💡 重点说明
this.doubleCount 是另一个 getter
如果你用 TypeScript,必须指定返回值类型,如 : number

2. 向 Getter 传递参数(返回一个函数)

❌ 错误方式(不能这样做)
// 不支持:getUserById(id) { ... }正确方式:返回一个函数
export const useUserListStore = defineStore('userList', {state: () => ({users: [{ id: 1, name: 'A' }, { id: 2, name: 'B' }]}),getters: {getUserById: (state) => {return (userId) => state.users.find((user) => user.id === userId)}}
})

注意

  • 返回函数的 getter 不会被缓存(不像 computed),每次调用都重新执行

  • 更适合用于 过滤、查找 等需要参数的场景

 3. 缓存变体:先筛选再返回函数

getActiveUserById(state) {const activeUsers = state.users.filter((u) => u.active)return (id) => activeUsers.find((u) => u.id === id)
}
适合场景:
当你要多次按 id 查询某个子集(如活跃用户)
手动做了第一步过滤以节省后续运算

 4. 访问其他 Store 的 Getter

import { useOtherStore } from './other-store'export const useStore = defineStore('main', {state: () => ({ localData: 10 }),getters: {otherGetter(state) {const otherStore = useOtherStore()return state.localData + otherStore.doubleValue // 使用别的 store 的 getter}}
})
你可以在 getter 中随意使用其他 store,就像在组件中那样要确保其他 store 已经定义好尽量避免循环依赖(Store A 调用 B,B 又回头调用 A)

 五、Action

 Action 相当于组件中的 method。它们可以通过 defineStore() 中的 actions 属性来定义,并且它们也是定义业务逻辑的完美选择。

 类似 getter,action 也可通过 this 访问整个 store 实例,并支持完整的类型标注(以及自动补全✨)不同的是,action 可以是异步的,你可以在它们里面 await 调用任何 API,以及其他 action!

 1、定义

1. 使用选项式 API 定义 Store
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', {state: () => ({count: 0}),actions: {increment() {this.count++}}
})
✅ 说明:
state: 返回一个对象,定义响应式数据  actions: 包含可以修改 state 的方法
这是 Pinia 的 选项式 API 写法,类似 Vue 组件的 data、methods🧩 2. 在组件中使用 store(有两种写法)
✅ 使用 setup()(组合式 API 风格)
import { useCounterStore } from '@/stores/counter'
export default defineComponent({setup() {const counterStore = useCounterStore()return { counterStore }},methods: {incrementAndPrint() {this.counterStore.increment()console.log('New Count:', this.counterStore.count)}}
})
📌 特点:
虽然这是在选项式组件中,但使用了 setup(),所以不需要 mapState() 或 mapActions() 映射
store 的属性都挂在 this.counterStore 上
✅ 不使用 setup(),使用 mapActions() 辅助函数
import { mapActions } from 'pinia'
import { useCounterStore } from '../stores/counter'export default {methods: {...mapActions(useCounterStore, ['increment']),...mapActions(useCounterStore, { myOwnName: 'increment' })}
}特点:将 store 中的 action 映射为当前组件的 method
this.increment() 实际调用的是 store.increment()
如果你不使用 setup(),这是官方推荐的写法

2. 订阅 Action 的执行:$onAction()

✅ 作用:

$onAction() 允许你拦截 action 的执行过程,记录日志、添加监控、处理错误等。

const unsubscribe = store.$onAction(({ name, args, after, onError }) => {const start = Date.now()console.log(`Start "${name}" with args:`, args)after((result) => {console.log(`Finished "${name}" in ${Date.now() - start}ms. Result:`, result)})onError((err) => {console.warn(`Failed "${name}" in ${Date.now() - start}ms. Error:`, err)})
})

📌 回调参数说明:

参数说明
name当前执行的 action 名称
args调用该 action 时传入的参数
after(fn)在 action 成功执行后触发的回调
onError(fn)在 action 执行失败或抛错时触发的回调

3. 解绑监听器(防止内存泄露)

 unsubscribe() // 手动移除订阅器

✅ 默认行为:

  • 如果 store 是在组件的 setup() 中创建的,$onAction() 会自动在组件卸载时移除监听器。

✅ 永久监听(不随组件销毁):

 store.$onAction(callback, true) // 第二个参数为 true

适用于:

  • 全局日志系统

  • 性能监控

  • 状态操作历史记录

 六、插件

1、什么是 Pinia 插件?

Pinia 插件是用来扩展所有 store 的功能的工具。它们可以:

  • 添加默认属性

  • 注入共享方法

  • 实现状态持久化、本地缓存、日志记录等功能

可以把插件理解为:给每个 Store 加“外挂”功能,类似 Vue 插件或 Vuex 插件。

插件 = 给每个 store 加功能的小外挂。

想象一下你有很多个 store,就像很多个员工。你想让每个员工都多一个新技能,比如:

  • 会自动保存工作成果(= 自动把 state 保存到 localStorage)

  • 每次干活都写日志(= action 调用时打印日志)

  • 都能说一句“你好老板”(= 添加一个公共方法)

你不用每次都手动写这段逻辑,而是通过 插件,一次性注入到所有 store 里,就像给每个员工配发一个新“工具包”。

2、插件能做什么?

Pinia 插件可以用来实现以下场景:

功能示例
状态持久化保存 store 到 localStorage
自动记录操作日志记录每次 action 调用的时间、参数
注入公共方法$toast()$confirm()
初始化共享状态比如全局主题、设置项
插件封装业务逻辑例如 API 请求封装、授权检测

 Pinia 插件是一个函数,可以选择性地返回要添加到 store 的属性。它接收一个可选参数,即 context

export function myPiniaPlugin(context) {context.pinia // 用 `createPinia()` 创建的 pinia。context.app // 用 `createApp()` 创建的当前应用(仅 Vue 3)。context.store // 该插件想扩展的 storecontext.options // 定义传给 `defineStore()` 的 store 的可选对象。// ...
}

然后用 pinia.use() 将这个函数传给 pinia

pinia.use(myPiniaPlugin)

插件只会应用于在 pinia 传递给应用后创建的 store,否则它们不会生效。

3、如何使用插件?

✅ 注册插件到 Pinia 实例

// main.js
import { createPinia } from 'pinia'
import piniaPlugin from './myPlugin'const pinia = createPinia()
pinia.use(piniaPlugin)

✅ 插件的基本结构

// myPlugin.js
export default function myPlugin({ store }) {// 添加一个自定义属性store.hello = 'world'// 添加一个共享方法store.sayHello = () => {console.log(`Hello from ${store.$id}`)}
}

使用后,你可以在任意 store 中访问:

const store = useMyStore()
store.sayHello() // 打印:Hello from myStore

4、插件能做的几件事(举例)

插件功能作用说明类比
自动保存 state 到本地页面刷新后还能恢复数据自动保存工作成果
打印日志每次调用 action 时记录操作打卡上下班
增加自定义方法比如 $toast() 弹窗提示提供快捷按钮
修改 store 行为比如封装通用校验定制化员工行为

5、插件和 store 有什么区别?

Store 是什么?插件是干嘛的?
管理状态(数据)和操作(actions)扩展每个 store 的功能
每个业务写一个全局使用一次,所有 store 都能用
比如 userStore, cartStore比如“保存功能”、“日志功能”

6、插件的真正价值

插件的核心作用是:统一增强所有 store 的功能,而不是在每个 store 里重复写。

举个例子:

如果你希望所有模块的状态都能自动保存到本地(比如购物车、用户设置等),你肯定不想在每个 store 里写一遍 localStorage.setItem()

所以,你就写一个插件,让每个 store 自动保存它自己的数据,只要注册一次,全局生效。

 

相关文章:

  • MDP相关内容
  • 贵州省棒球运动发展中长期规划(2024-2035)·棒球1号位
  • 第二天 网络与通信协议
  • 【c++】 我的世界
  • 汽车加气站操作工考试知识点总结
  • 手机端调试工具 eruda 使用方法
  • C++ 中的 `it->second` 和 `it.second`:迭代器与对象访问的微妙区别
  • 图片转文字-Tesseract-OCR,完成文字转换。
  • vtkSmartPointer<vtkPolyData> 常用的函数方法
  • 二项式反演及其代数证明
  • 生物化学笔记:神经生物学概论12 大脑全景图 知觉、行为和语言 注意力
  • LeetCode105_从先序与中序遍历序列构造二叉树
  • Napkin 简易教程
  • async/await的另一种食用方法
  • C 语言网络编程问题:E1696 无法打开 源 文件 “sys/socket.h“
  • sunset: dawn靶场渗透
  • 如何创建RDD
  • 第三章、RL Games:High performance RL library
  • grpc到底是啥! ! !!
  • 使用 pgrep 杀掉所有指定进程
  • 城管给商户培训英语、政银企合作纾困,上海街镇这样优化营商环境
  • 视频|漫画家寂地:古老丝路上的文化与交流留下的独特印记
  • 吴清:加强监管的同时传递监管温度,尽力帮助受影响企业应对美加征关税的冲击
  • 中国公民免签赴马来西亚的停留天数如何计算?使馆明确
  • 联合国秘书长古特雷斯呼吁印巴保持最大克制
  • 言短意长|如何看待“订不到酒店的游客住进局长家”这件事