手把手教你使用Vue3指定状态管理库--Pinia

news2025/1/12 0:54:06

什么是 Pinia

Pinia 与 Vuex 一样,是作为 Vue 的“状态存储库”,用来实现 跨页面/组件 形式的数据状态共享。

在平时的开发过程中,Vue 组件之间可以通过 PropsEvents 实现组件之间的消息传递,对于跨层级的组件也可以通过 EventBus 来实现通信。但是在大型项目中,通常需要在浏览器 保存多种数据和状态,而使用 Props/Events 或者 EventBus 是很难维护和扩展的。所以才有了 Vuex 和 Pinia。

Pinia 为何能取代 Vuex

作为 Vue 开发者都知道,Vuex 作为 Vue 的老牌官方状态库,已经和 Vue 一起存在了很长时间,为什么现在会被 Pinia 取代呢?

官方的说法主要是以下几点:

1.取消 mutations。因为在大部分开发者眼中,mutations 只支持 同步修改状态数据,而 actions 虽然支持 异步,却依然要在内部调用 mutations 去修改状态,无疑是非常繁琐和多余的
2.所有的代码都是 TypeScript 编写的,并且所有接口都尽可能的利用了 TypeScript 的 类型推断,而不像 Vuex 一样需要自定义 TS 的包装器来实现对 TypeScript 的支持
3.不像 Vuex 一样需要在实例/Vue原型上注入状态依赖,而是通过直接引入状态模块、调用 getter/actions 函数来完成状态的更新获取;并且因为自身对 TypeScript 的良好支持和类型推断,开发者可以享受很优秀的代码提示
4.不需要预先注册状态数据,默认情况下都是根据代码逻辑自动处理的;并且可以在使用中随时注册新的状态
5.没有 Vuex 的 modules 嵌套结构,所有状态都是扁平化管理的。也可以理解为 pinia 注册的状态都类似 vuex 的 module,只是 pinia 不需要统一的入口来注册所有状态模块
6.虽然是扁平化的结构,但是依然支持 每个状态之间的互相引用和嵌套
7.不需要 namespace 命名空间,得利于扁平化结构,每个状态在注册时即使没有声明状态模块名称,pinia 也会默认对它进行处理

总结一下就是:Pinia 在实现 Vuex 全局状态共享的功能前提下,改善了状态存储结构,优化了使用方式,简化了 API 设计与规范;并且基于 TypeScript 的类型推断,为开发者提供了良好的 TypeScript 支持与代码提示。

如何使用

至于 Pinia 在项目中的安装,大家应该都知道,直接通过包管理工具安装即可。

1. 注册 Pinia 实例

以 Vue 3 项目为例,只需要在入口文件 main.ts 中引入即可完成 Pinia 的注册。

import { createApp } from 'vue'
import { createPinia } from 'pinia'

const app = createApp(App)
const pinia = createPinia()
app.use(pinia) 

当然,因为支持 createApp 支持 链式调用,所以也可以直接写成 createApp(App).use(createPinia()).mount('#app').

此时 createPinia() 创建的是一个根实例,在 app.use 的时候会在 app 中注入该实例,并且配置一个 app.config.globalProperties.$pinia 也指向该实例。

2. 定义状态 Store

在注册一个 Pinia 状态模块的时候,可以通过 defineStore 方法创建一个 状态模块函数(之所以是函数,是因为后面调用的时候需要通过函数的形式获取到里面的状态)。

deineStore 函数的 TypeScript 定义如下:

function defineStore<Id, S, G, A>(id, options): StoreDefinition<Id, S, G, A>
function defineStore<Id, S, G, A>(options): StoreDefinition<Id, S, G, A>
function defineStore<Id, SS>(id, storeSetup, options?): StoreDefinition<Id, _ExtractStateFromSetupStore<SS>, _ExtractGettersFromSetupStore<SS>, _ExtractActionsFromSetupStore<SS>>

type Id = ID extends string
type storeSetup = () => SS
type options = Omit<DefineStoreOptions<Id, S, G, A>, "id"> | DefineStoreOptions<Id, S, G, A> | DefineSetupStoreOptions<Id, _ExtractStateFromSetupStore<SS>, _ExtractGettersFromSetupStore<SS>, _ExtractActionsFromSetupStore<SS>> 

可以看到该函数最多接收 3个参数,但是我们最常用的一般都是第一种或者第二种方式。这里以 第一种方式 例,创建一个状态模块函数:

// 该部分节选字我的开源项目 vite-vue-bpmn-process
import { defineStore } from 'pinia'
import { defaultSettings } from '@/config'
import { EditorSettings } from 'types/editor/settings'

const state = {editorSettings: defaultSettings
}

export default defineStore('editor', {state: () => state,getters: {getProcessDef: (state) => ({processName: state.editorSettings.processName,processId: state.editorSettings.processId}),getProcessEngine: (state) => state.editorSettings.processEngine,getEditorConfig: (state) => state.editorSettings},actions: {updateConfiguration(conf: Partial<EditorSettings>) {this.editorSettings = { ...this.editorSettings, ...conf }}}
}) 

其中的 options 配置项包含三个部分:

  • state:状态的初始值,推荐使用的是一个 箭头函数,方便进行类型推断
  • getters:状态的获取,是一个对象格式;推荐配置为每个 getters 的对象属性为 箭头函数,方便进行类型推断;在使用时等同于获取该函数处理后的 state 状态结果;并且与 Vue 的计算属性一样,该方法也是惰性的,具有缓存效果
  • actions:类似 Vue 中的 methods 配置项,支持异步操作,主要作用是 处理业务逻辑并更新状态数据;另外,此时的 actions 是一个 函数集合对象,与 getters 不同的是 不建议使用箭头函数并且函数内部的 this 就指向当前 store 的 state。

注意:getters 的函数定义中 第一个参数就是当前 store 的状态数据 state,而 actions 中的函数参数为 实际调用时传递的参数,可以传递多个,内部通过 this 上下文 直接访问 state 并进行更新。

3. 组件使用(配合 setup)

众所周知,vue 3 最大的亮点之一就是 组合式API(Composition API),所以我们先以组件配合 setup 使用。

import { defineComponent, ref, computed } from 'vue'
import { storeToRefs } from 'pinia'
import { EditorSettings } from 'types/editor/settings'
import editorStore from '@/store/editor'

export default defineComponent({setup(props) {const editor = editorStore()// 直接获取 state 状态const { editorSettings } = storeToRefs(editor)// 使用 computedconst editorSettings = computed(() => editor.editorSettings)// gettersconst prefix = editor.getProcessEngine// 更新方式 1:调用 actionseditorStore.updateConfiguration({})// 更新方式 2:直接改变 state 的值editorStore.editorSettings = {}// 更新方式 3:调用 $patcheditorStore.$patch((state) => {state.editorSettings = {}})return {editorStore}}
}) 

这里对以上几种处理方式进行说明:

获取值:

1.可以通过 解构 获取 state 定义的数据,但是 解构会失去响应式,所以需要用 storeToRefs 重新对其进行响应式处理
2.通过 computed 计算属性,好处是 可以对 state 中的状态数据进行组合
3.通过定义的 getters 方法来获取值,这种方式获取的结果本身就是 响应式的,可以直接使用

更新值:

1.首先是可以 直接改变 state 的状态值,缺点是多次使用容易有重复代码,且不好维护;也会影响代码的可读性
2.通过定义的 actions 更新,也算是推荐方法之一;在后续迭代和扩展中,只需要维护好 store 中的代码即可
3.$patch: 这个方式 可以接收一个对象或者函数,但是 推荐使用箭头函数(函数参数为状态数据 state);因为如果是对象,则需要根据新数据和当前状态 重建整个 state,增加了很多的性能损耗;而使用箭头函数,其实就与 actions 中的方式类似,可以 按代码逻辑修改指定的状态数据

4. 组件使用(没有 setup)

而在传统的 optionsAPI 模式的组件中(也没有配置 setup),Pinia 也提供了与 Vuex 一致的 API:mapState,mapGetters,mapActions,另外还增加了 mapStores 用来访问所有已注册的 store 数据,新增了 mapWritableState 用来 定义可更新状态;也因为 pinia 没有 mutations,所以也取消了 mapMutations 的支持。

mapGetters 也只是为了方便迁移 Vuex 的组件代码,后面依然建议 使用 mapState 替换 mapGetters

<template>
	<div><p>{{ settings }}</p><p>{{ processEngine }}</p><button @click="updateConfiguration({})">调用 action</button><button @click="update">调用 mapWritableState</button></div>
</template>
<script> import { defineComponent, ref, storeToRefs } from 'vue'import { mapState, mapActions, mapWritableState } from 'pinia'import editorStore from '@/store/editor'export default defineComponent({computed: {...mapState(editorStore, {settings: 'editorSettings',processEngine: (state) => `This process engine is ${state.editorSettings.processEngine}`}),...mapWritableState(editorStore, ['editorSettings'])},methods: {...mapActions(editorStore, ['updateConfiguration']),update() {this.editorSettings.processEngine = "xxx"}}}) </script> 

mapStores 用来访问 所有已注册 store 状态。假设我们除了上文定义的 editor,还定义了一个 id 为 modeler 的 store,则可以这么使用:

import editor from '@/store/editor'
import modeler from '@/store/modeler'
export default defineComponent({
computed: {
...mapStores(editor, modeler)
},
methods: {
async updateAll() {
if (this.editorStore.processEngine === 'camunda') {
await this.modelerStore.update()
}
}
}
}) 

其中引用的所有 store,都可以通过 id + ‘Store’ 的形式在 Vue 实例中访问到。

5. 互相引用

因为 Pinia 本身是支持各个 store 模块互相引用的,所以在定义的时候可以直接引用其他 store 的数据进行操作。

例如我们这里根据 editor store 创建一个 modeler store

import { defineStore } from 'pinia'
import editor from '@/store/editor'

export default defineStore('editor', {state: () => ({element: null,modeler: null}),actions: {updateElement(element) {const editorStore = editor()if (!editorStore.getProcessEngine) {editorStore.updateConfiguration({ processEngine: 'camunda' })}this.element = element}}
}) 

6. 脱离 store 模块和组件使用

因为 Pinia 的每个 store 模块都是依赖 vue 应用和 pinia 根实例的,在组件内部使用时因为 Vue 应用和 pinia 根实例肯定都已经是 注册完成处于活动状态中的,所以可以直接通过调用对应的 store 状态模块函数即可。

但是在脱离 store 模块与组件,直接在外部的纯函数中使用时,则需要注意 store 状态模块函数的调用时机。

以官方的示例来看:

import { createRouter } from 'vue-router'
const router = createRouter({// ...
})

// ❌ 根据导入的顺序,这将失败
const store = useStore()

router.beforeEach((to, from, next) => {// 我们想在这里使用 store if (store.isLoggedIn) next()else next('/login')
})

router.beforeEach((to) => {// ✅ 这将起作用,因为路由器在之后开始导航 // 路由已安装,pinia 也将安装const store = useStore()if (to.meta.requiresAuth && !store.isLoggedIn) return '/login'
}) 

直接在js模块的执行中 直接调用是可能会报错的,因为此时可能在 import router 的时候 还没有调用 createApp 和 createPinia 创建对应的应用实例和 pinia 根实例,所以无法使用。

而在路由导航的拦截器中使用时,因为 路由拦截触发时,应用和 pinia 根实例肯定已经全部实例化完毕,才可以正常使用。

所以 如果是在外部的 hooks 函数或者 utils 工具函数等纯函数模块中使用 store 数据时,最好是定义一个函数方法导出,在组件或者 store 模块中调用该方法,保证此时能正确执行

最后

总的来说,Pinia 作为 Vue 官方推荐的状态库,配合 Vue 3 的组合式 API,可以更好的实现项目中各种数据状态的管理,而不是像以前使用 Vuex 一样通过 modules 的形式注册各种状态。Pinia 对于抽离逻辑进行复用(hooks),简化使用方式来说,比之前的 Vuex 好了很多倍;加上良好的类型支持与代码提示,让我们在开发过程中可以省去很多前置工作,也是对我们的开发效率的一种提升吧。

当然,、Vue DevTools 在更新之后,也实现了对 Pinia 的支持。

最后

整理了一套《前端大厂面试宝典》,包含了HTML、CSS、JavaScript、HTTP、TCP协议、浏览器、VUE、React、数据结构和算法,一共201道面试题,并对每个问题作出了回答和解析。

有需要的小伙伴,可以点击文末卡片领取这份文档,无偿分享

部分文档展示:



文章篇幅有限,后面的内容就不一一展示了

有需要的小伙伴,可以点下方卡片免费领取

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/50233.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Java并发编程实战读书笔记三

第七章 取消和关闭 Java没有提供任何机制来安全的终止线程&#xff0c;虽然 Thread.stop 和 suspend 等方法提供了这样的机制&#xff0c;但由于存在着一些严重的陷&#xff0c;因此应该避免使用 7.1任务取消 7.1.1 中断 取消任务中生产者使用了队列的put操作导致阻塞后任务…

day14_类中成员之构造器

由来 我们发现我们new完对象时&#xff0c;所有成员变量都是默认值&#xff0c;如果我们需要赋别的值&#xff0c;需要挨个为它们再赋值&#xff0c;太麻烦了。我们能不能在new对象时&#xff0c;直接为当前对象的某个或所有成员变量直接赋值呢。可以&#xff0c;Java给我们提…

工作两年,没想到靠Python搞副业让我实现了财务自由

前言 国庆假期和好友聚会&#xff0c;聊了各自近两年的变化&#xff0c;朋友的经历让我大吃一惊&#xff01; 2年前他还是月薪5千的小编&#xff0c;现在轻松实现月入5万的小目标。 &#xff08;文末送读者福利&#xff09; 原来是利用空余时间学会了Python编程&#xff0c…

TPM零知识学习二—— 相关链接和页面

TPM2社区的主页地址为&#xff1a;https://tpm2-software.github.io/ 页面如下&#xff1a; 主页中提供了很多有用的链接&#xff0c;包括&#xff1a; Software | tpm2-software community 页面如下&#xff1a; External | tpm2-software community 页面如下&#xff1a;…

《计算机体系结构量化研究方法》1.8 性能的测量、报告和汇总

引入 1、一些概念 响应时间&#xff1a;手机用户所关注的“速度”&#xff0c;也就是一个事件从启动到完成的时间&#xff0c;又称为执行时间。 吞吐量&#xff1a;给定时间内完成的总工作量&#xff0c;WSC的操作人员关注这个。 执行时间是性能的倒数&#xff08;即执行时间…

DSP之Controlsuit和C2000Ware下载安装

DSP之Controlsuit和C2000Ware下载安装 Controlsuit 用于 C2000TM 微处理器的 controlSUITETM 是一套全面的软件基础设施和软件工具集&#xff0c;旨在最大程度地缩短软件开发时间。从特定于器件的驱动程序和支持软件到复杂系统应用中的完整系统示例&#xff0c;controlSUITET…

Redis击穿、穿透、雪崩

Redis 的基本概念 在没有添加 Redis 的时候&#xff0c;后端的查询流程是&#xff1a; 用户访问页面-请求后端服务-经过逻辑处理后&#xff0c;去数据库查询信息。 在添加 Redis 的之后&#xff0c;后端的查询流程是&#xff1a; 用户访问页面-请求后端服务-经过逻辑处理后&a…

超强AI绘图-文生图

一、前言 前面汇总了四种AI绘图的方法&#xff0c;详情可以点击下图跳转到文章《AI绘画合集&#xff08;11月汇总&#xff09;》。主要内容是&#xff1a;具象意象派AI作图、AI绘制专属动漫头像、基于ViLG模型AI 作画。点击下面文章跳转&#xff01; 《AI绘图—对中文拟合度…

LabVIEW创建类 3

LabVIEW创建类 3 继承 通过“继承”可在现有类的基础上创建新类。若创建一个新的LabVIEW类并将它设置为继承另一个类的数据及成员VI&#xff0c;这个新类将可使用它所继承的类中“公共”及“保护”型的成员VI。这个新类还可添加自己的数据和成员VI以丰富其功能。例如&#xf…

聚观早报 | 脸书泄露数据被罚20亿;iPhone15将全系支持灵动岛

今日要闻&#xff1a;脸书泄露数据被罚20亿&#xff1b;iPhone15将全系支持灵动岛&#xff1b;540万推特用户数据被盗&#xff1b;罗永浩入局tiktok&#xff1b;特斯拉研发改款Model3脸书泄露数据被罚20亿 据报道&#xff0c;脸书因泄露5亿用户数据&#xff0c;被欧洲监管机构罚…

Starday为什么是跨境电商卖家的不二之选?

据国内海关统计显示&#xff0c;近5年来&#xff0c;中国跨境电商规模增长近10倍&#xff0c;年增长率在30%以上&#xff0c;占国际贸易近40%。基于流量模式的跨境电商直播、垂直跨境电商等新模式蓬勃发展&#xff0c;近几年跨境电商一直不断地在深度融合发展&#xff0c;加之疫…

WSL Ubuntu20.04安装pycairo指南

环境说明 wsl Ubuntu20.04 走过的一些可能有用的弯路 由于pycairo要求python3.7&#xff0c;但是之前Ubuntu上有个3.6的python环境&#xff0c;所以就安装了python3.8&#xff1a; sudo apt install python3.8然后python3命令还是链接到python3.6&#xff0c;结果就yongln …

Stateflow状态转移练习

文章目录1.外部转移与内部转移1.1 stateflow1.2 分析2.外部转移内部转移超转移2.1 stateflow2.2 video1.外部转移与内部转移 1.1 stateflow 1.2 分析 首先是 xyz0&#xff0c;然后进入 A 状态&#xff0c;此时 A 的 en 激活&#xff0c;xx1&#xff0c;即 x1其次进入默认状态…

聊天软件im即时通讯源码酷信视酷,在线语音视屏通话端对端加密聊天,支持7端互通带部署教程

APP端有安卓端苹果端&#xff0c;PC端&#xff0c;web端&#xff0c;管理端有PC后台&#xff01; .产品为独立开发&#xff0c;非网上下载不能用的产品&#xff01; .高端产品&#xff0c;即时聊天软件技术难度大&#xff0c;请不要拿网络其他聊天软件来对比&#xff0…

多线程,了解-概念-实现方式-常见方法-安全问题-死锁-生产者消费者

了解 简单了解多线程 是指从软件或者硬件上实现多个线程并发执行的技术。 具有多线程能力的计算机因有硬件支持而能够在同一时间执行多个线程&#xff0c;提升性能。 简单了解多线程 简单了解多线程 简单了解多线程 简单了解多线程 概念 线程相关的概念 并行&#xff1a;在同…

【Linux进阶】-- 1.python脚本实现守护进程daemon调度,启停等

"怀斯曼不蒸汽啊"1. Linux守护进程介绍1.1 查看进程2. python指令for linux2.1 运行shell命令3. 代码讲解3.1 创建守护进程文件pre_deal.py3.2 用脚本运行3.3 用一个python程序来管理该任务任务介绍: 首先python创建文件1,作用是创建一个守护进程,内容是持续输出时间…

<C++>手撕搜索二叉树

目录 一、搜索二叉树的性质 二、搜索二叉树的结构定义 三、手撕搜索二叉树非递归 1&#xff09;Insert() 2&#xff09;Find() 3&#xff09;Erase() 4&#xff09;InOder() 5&#xff09;BSTree(const BSTree& t) 拷贝构造 6&#xff09;~BSTree()析构函数 四、手…

【学习笔记65】JavaScript的继承

一、认识继承 function Person(name) {this.name name}Person.prototype.sayName () > {console.log(name)}function Stu(age) {this.age age}const s new Stu(18)console.log(s) // {age: 18} 说明&#xff1a; 想让s这个对象内部有一个name属性, 并且可以使用 sayN…

双功能连接试剂:Alkyne hydrazide,炔烃-酰肼 主要特点进行分享

Alkyne hydrazide物理参数&#xff1a; CAS号&#xff1a;N/A |英文名&#xff1a;Alkyne hydrazide | 中文名&#xff1a;炔烃-酰肼 货号&#xff1a;X-CL-1132 分子式&#xff1a;C6H11ClN2O 分子量&#xff1a;162.62 纯度&#xff1a;95% 外形&#xff1a;淡黄色或白…

企业级的商用远程控制软件有哪些

现在远程控制软件&#xff0c;可选性还是比较大的。针对个人用户市场&#xff0c;也有不少免费软件。 企业用户的需求和个人用户的需求&#xff0c;差别较大。个人用户市场&#xff0c;主要诉求是免费、好用。企业用户&#xff0c;主要关注安全、管理功能、性能、价格等因素。…