Vue快速入门(六)——Vue3 状态管理 - Pinia(一)

目录

一、什么是Pinia

二、对比 Vuex

三、安装Pinia

四、核心概念——Store

1、什么是Store

2、什么时候使用 Store

3、什么时候不应该使用 Store

4、定义 Store

5、使用 Store

6、从 Store 解构

五、核心概念——State

1、定义State

2、TypeScript

3、访问 state

4、重置 state

5、变更 state

6、替换 state

7、订阅 state


一、什么是Pinia

Pinia 是 Vue 的专属的最新状态管理库 ,是 Vuex 状态管理工具的替代品,同时支持 Vue 2 和 Vue 3。

为什么取名Pinia?

Pinia (发音为 /piːnjʌ/,类似英文中的 “peenya”) 是最接近有效包名 piña (西班牙语中的 pineapple,即“菠萝”) 的词。 菠萝花实际上是一组各自独立的花朵,它们结合在一起,由此形成一个多重的水果。 与 Store 类似,每一个都是独立诞生的,但最终它们都是相互联系的。 它(菠萝)也是一种原产于南美洲的美味热带水果。

二、对比 Vuex

1、提供了一个更简单的 API(去掉了mutation)

2、提供了符合组合式风格的 API

3、去掉了modules的概念,每一个store都是一个独立的模块

4、搭配 TypeScript 更加友好,提供可靠的类型推断

三、安装Pinia

用你喜欢的包管理器安装 pinia

yarn add pinia
# 或者使用 npm
npm install pinia

TIP

如果你的应用使用的 Vue 版本低于 2.7,你还需要安装组合式 API 包:@vue/composition-api

如果你正在使用 Vue CLI,你可以试试这个非官方插件。

创建一个 pinia 实例 (根 store) 并将其传递给应用:

import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'const pinia = createPinia()
const app = createApp(App)app.use(pinia)
app.mount('#app')

如果你使用的是 Vue 2,你还需要安装一个插件,并在应用的根部注入创建的 pinia

import { createPinia, PiniaVuePlugin } from 'pinia'Vue.use(PiniaVuePlugin)
const pinia = createPinia()new Vue({el: '#app',// 其他配置...// ...// 请注意,同一个`pinia'实例// 可以在同一个页面的多个 Vue 应用中使用。pinia,
})

四、核心概念——Store

1、什么是Store

Store (如 Pinia) 是一个保存状态和业务逻辑的实体,它并不与你的组件树绑定。换句话说,它承载着全局状态。它有点像一个永远存在的组件,每个组件都可以读取和写入它。它有三个概念,stategetter action,我们可以假设这些概念相当于组件中的 data computed methods

2、什么时候使用 Store

一个 Store 应该包含可以在整个应用中访问的数据。这包括在许多地方使用的数据,例如显示在导航栏中的用户信息,以及需要通过页面保存的数据,例如一个非常复杂的多步骤表单。

另一方面,你应该避免在 Store 中引入那些原本可以在组件中保存的本地数据,例如,一个元素在页面中的可见性。

并非所有的应用都需要访问全局状态,但如果你的应用确实需要一个全局状态,那 Pinia 将使你的开发过程更轻松。

3、什么时候应该使用 Store

有的时候我们会过度使用 store。如果觉得应用程序的 store 过多,你可能需要重新考虑使用 store 的目的。例如其中一些逻辑应该只是组合式函数,或者应该只是组件的本地状态。

4、定义 Store

Store 是用 defineStore() 定义的,它的第一个参数要求是一个独一无二的名字:

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

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

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

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)。

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 ,开发工具和其他插件的正常运行。

Setup store 比 Option Store 带来了更多的灵活性,因为你可以在一个 store 内创建侦听器,并自由地使用任何组合式函数。不过,请记住,使用组合式函数会让 SSR 变得更加复杂。

Setup store 也可以依赖于全局提供的属性,比如路由。任何应用层面提供的属性都可以在 store 中使用 inject() 访问,就像在组件中一样:

import { inject } from 'vue'
import { useRoute } from 'vue-router'
import { defineStore } from 'pinia'export const useSearchFilters = defineStore('search-filters', () => {const route = useRoute()// 这里假定 `app.provide('appProvided', 'value')` 已经调用过const appProvided = inject('appProvided')// ...return {// ...}
})

WARNING

不要返回像 route 或 appProvided (上例中)之类的属性,因为它们不属于 store,而且你可以在组件中直接用 useRoute() 和 inject('appProvided') 访问。

你应该选用哪种语法?

和在 Vue 中如何选择组合式 API 与选项式 API 一样,选择你觉得最舒服的那一个就好。两种语法都有各自的优势和劣势。Option Store 更容易使用,而 Setup Store 更灵活和强大。

5、使用 Store

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

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

你可以定义任意多的 store,但为了让使用 pinia 的益处最大化 (比如允许构建工具自动进行代码分割以及 TypeScript 推断),你应该在不同的文件中去定义 store

一旦 store 被实例化,你可以直接访问在 store 的 stategetters 和 actions 中定义的任何属性。我们将在后续章节继续了解这些细节,目前自动补全将帮助你使用相关属性。

请注意,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>

6、从 Store 解构

为了从 store 中提取属性时保持其响应性,你需要使用 storeToRefs()。它将为每一个响应式属性创建引用。当你只使用 store 的状态而不调用任何 action 时,它会非常有用。请注意,你可以直接从 store 中解构 action,因为它们也被绑定到 store 上:

<script setup>
import { storeToRefs } from 'pinia'
const store = useCounterStore()
// `name` 和 `doubleCount` 是响应式的 ref
// 同时通过插件添加的属性也会被提取为 ref
// 并且会跳过所有的 action 或非响应式 (不是 ref 或 reactive) 的属性
const { name, doubleCount } = storeToRefs(store)
// 作为 action 的 increment 可以直接解构
const { increment } = store
</script>

五、核心概念——State

1、定义State

在大多数情况下,state 都是你的 store 的核心。人们通常会先定义能代表他们 APP 的 state。在 Pinia 中,state 被定义为一个返回初始状态的函数。这使得 Pinia 可以同时支持服务端和客户端。

import { defineStore } from 'pinia'const useStore = defineStore('storeId', {// 为了完整类型推理,推荐使用箭头函数state: () => {return {// 所有这些属性都将自动推断出它们的类型count: 0,name: 'Eduardo',isAdmin: true,items: [],hasChanged: true,}},
})

TIP

如果你使用的是 Vue 2,你在 state 中创建的数据与 Vue 实例中的 data 遵循同样的规则,即 state 对象必须是清晰的,当你想向其添加新属性时,你需要调用 Vue.set() 。

2、TypeScript

你并不需要做太多努力就能使你的 state 兼容 TS。确保启用了 strict,或者至少启用了 noImplicitThis,Pinia 将自动推断您的状态类型! 但是,在某些情况下,您应该帮助它进行一些转换:

const useStore = defineStore('storeId', {state: () => {return {// 用于初始化空列表userList: [] as UserInfo[],// 用于尚未加载的数据user: null as UserInfo | null,}},
})interface UserInfo {name: stringage: number
}

如果你愿意,你可以用一个接口定义 state,并添加 state() 的返回值的类型。

interface State {userList: UserInfo[]user: UserInfo | null
}const useStore = defineStore('storeId', {state: (): State => {return {userList: [],user: null,}},
})interface UserInfo {name: stringage: number
}

3、访问 state

默认情况下,你可以通过 store 实例访问 state,直接对其进行读写。

const store = useStore()store.count++

注意,新的属性如果没有在 state() 中被定义,则不能被添加。它必须包含初始状态。例如:如果 secondCount 没有在 state() 中定义,我们无法执行 store.secondCount = 2

4、重置 state

使用选项式 API 时,你可以通过调用 store 的 $reset() 方法将 state 重置为初始值。

const store = useStore()store.$reset()

在 $reset() 内部,会调用 state() 函数来创建一个新的状态对象,并用它替换当前状态。

Setup Stores 中,您需要创建自己的 $reset() 方法:

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

使用选项式 API 的用法

在下面的例子中,你可以假设相关 store 已经创建了:

// 示例文件路径:
// ./src/stores/counter.jsimport { defineStore } from 'pinia'const useCounterStore = defineStore('counter', {state: () => ({count: 0,}),
})

如果你不能使用组合式 API,但你可以使用 computedmethods,...,那你可以使用 mapState() 辅助函数将 state 属性映射为只读的计算属性:

import { mapState } from 'pinia'
import { useCounterStore } from '../stores/counter'export default {computed: {// 可以访问组件中的 this.count// 与从 store.count 中读取的数据相同...mapState(useCounterStore, ['count'])// 与上述相同,但将其注册为 this.myOwnName...mapState(useCounterStore, {myOwnName: 'count',// 你也可以写一个函数来获得对 store 的访问权double: store => store.count * 2,// 它可以访问 `this`,但它没有标注类型...magicValue(store) {return store.someGetter + this.count + this.double},}),},
}

可修改的 state

如果你想修改这些 state 属性 (例如,如果你有一个表单),你可以使用 mapWritableState() 作为代替。但注意你不能像 mapState() 那样传递一个函数:

import { mapWritableState } from 'pinia'
import { useCounterStore } from '../stores/counter'export default {computed: {// 可以访问组件中的 this.count,并允许设置它。// this.count++// 与从 store.count 中读取的数据相同...mapWritableState(useCounterStore, ['count'])// 与上述相同,但将其注册为 this.myOwnName...mapWritableState(useCounterStore, {myOwnName: 'count',}),},
}

TIP

对于像数组这样的集合,你并不一定需要使用 mapWritableState()mapState() 也允许你调用集合上的方法,除非你想用 cartItems = [] 替换整个数组。

5、变更 state

除了用 store.count++ 直接改变 store,你还可以调用 $patch 方法。它允许你用一个 state 的补丁对象在同一时间更改多个属性:

store.$patch({count: store.count + 1,age: 120,name: 'DIO',
})

不过,用这种语法的话,有些变更真的很难实现或者很耗时:任何集合的修改(例如,向数组中添加、移除一个元素或是做 splice 操作)都需要你创建一个新的集合。因此,$patch 方法也接受一个函数来组合这种难以用补丁对象实现的变更。

store.$patch((state) => {state.items.push({ name: 'shoes', quantity: 1 })state.hasChanged = true
})

两种变更 store 方法的主要区别是,$patch() 允许你将多个变更归入 devtools 的同一个条目中。同时请注意,直接修改 state$patch() 也会出现在 devtools 中,而且可以进行 time travel (在 Vue 3 中还没有)。

6、替换 state

不能完全替换掉 store 的 state,因为那样会破坏其响应性。但是,你可以 patch 它。

// 这实际上并没有替换`$state`
store.$state = { count: 24 }
// 在它内部调用 `$patch()`:
store.$patch({ count: 24 })

你也可以通过变更 pinia 实例的 state 来设置整个应用的初始 state。这常用于 SSR 中的激活过程。

pinia.state.value = {}

7、订阅 state

类似于 Vuex 的 subscribe 方法,你可以通过 store 的 $subscribe() 方法侦听 state 及其变化。比起普通的 watch(),使用 $subscribe() 的好处是 subscriptions 在 patch 后只触发一次 (例如,当使用上面的函数版本时)。

cartStore.$subscribe((mutation, state) => {// import { MutationType } from 'pinia'mutation.type // 'direct' | 'patch object' | 'patch function'// 和 cartStore.$id 一样mutation.storeId // 'cart'// 只有 mutation.type === 'patch object'的情况下才可用mutation.payload // 传递给 cartStore.$patch() 的补丁对象。// 每当状态发生变化时,将整个 state 持久化到本地存储。localStorage.setItem('cart', JSON.stringify(state))
})

默认情况下,state subscription 会被绑定到添加它们的组件上 (如果 store 在组件的 setup() 里面)。这意味着,当该组件被卸载时,它们将被自动删除。如果你想在组件卸载后依旧保留它们,请将 { detached: true } 作为第二个参数,以将 state subscription 从当前组件中分离

<script setup>
const someStore = useSomeStore()
// 此订阅器即便在组件卸载之后仍会被保留
someStore.$subscribe(callback, { detached: true })
</script>

TIP

你可以在 pinia 实例上使用 watch() 函数侦听整个 state。

watch(pinia.state,(state) => {// 每当状态发生变化时,将整个 state 持久化到本地存储。localStorage.setItem('piniaState', JSON.stringify(state))},{ deep: true }
)

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

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

相关文章

【代码随想录训练营第42期 Day38打卡 - 动态规划Part6 - LeetCode 322. 零钱兑换 279.完全平方数 139.单词拆分

目录 一、做题心得 二、题目与题解 题目一&#xff1a;322. 零钱兑换 题目链接 题解&#xff1a;动态规划--完全背包 题目二&#xff1a; 279.完全平方数 题目链接 题解&#xff1a;动态规划--完全背包 题目三&#xff1a;139.单词拆分 题目链接 题解&#xff1a;动…

微信小程序的遍历和事件的简单案例

遍历和事件的简单案例 在微信小程序中&#xff0c;你可以通过为组件添加事件来实现交互功能。当用户触发这些事件时&#xff0c;小程序会执行相应的处理函数。下面是一个示例&#xff0c;展示如何在微信小程序中遍历标签并为其添加点击事件&#xff1a; <view><view …

qt-16可扩展对话框--隐藏和展现

可扩展对话框 知识点extension.hextension.cppmain.cpp运行图初始化隐藏展现--点击--详细按钮 知识点 MainLayout->setSizeConstraint(QLayout::SetFixedSize);//固定窗口大小 extension.h #ifndef EXTENSION_H #define EXTENSION_H#include <QDialog>class Extens…

你是如何更精准地指引模型,激发其无尽的创造力?

随着大型语言模型日益凸显其重要性&#xff0c;发掘并充分利用它们的潜力&#xff0c;很大程度上依赖于我们如何巧妙构思和构造指令——即Prompt的精炼艺术。优化Prompt撰写技巧&#xff0c;将能够更好地引导大模型&#xff0c;为各类应用场景生成高质量的文本输出。分享出你的…

[数据库][知识]SQL Server、MySQL 和 Oracle 的默认端口和数据库链接

SQL Server、MySQL 和 Oracle 的默认端口号、连接 URL 和驱动类名。以下是对每个数据库连接信息的简要说明&#xff1a; SQL Server 默认端口号&#xff1a;1433JDBC URL 格式&#xff1a;jdbc:sqlserver://localhost:1433;DatabaseNamedbnameJDBC 驱动类名&#xff1a;com.mic…

【STM32】C语言基础补充

学习过程中发现自己好些需要用到的C语言语法、特征都不太熟练了&#xff0c;特意记录一下&#xff0c;免得忘记了&#xff0c;以后遇到了新的也会继续更新 目录 1 全局变量 2 结构体 3 静态变量 4 memset()函数 5 使用8位的存储器存16位的数 1 全局变量…

Excel“取消工作表保护”忘记密码并恢复原始密码

文章目录 1.前言2.破解步骤3. 最终效果4.参考文献 1.前言 有时候别人发来的Excel中有些表格不能编辑&#xff0c;提示如下&#xff0c;但是又不知道原始密码 2.破解步骤 1、打开您需要破解保护密码的Excel文件&#xff1b; 2、依次点击菜单栏上的视图—宏----录制宏&#xf…

Keil C51 插件 快速复制一行

此插件解决的问题 目前 Keil 软件 只可以 先选中一行&#xff0c;再Ctrl C Ctrl V , 太麻烦了 Keil 插件 -- Python 代码 import sys# 插入当前行内容 def insert_line(current_file_path, line_number):line_to_insert # 读取文件内容with open(current_file_path, r, enc…

微信小游戏授权问题

微信小程序获取用户相关信息的接口&#xff0c;如wx.getUserCloudStorage&#xff0c;报错&#xff1a;please go to mp to announce your privacy usage。 需要在微信公众平台设置用户隐私保护。

APP支付宝授权获取code uniapp

1.点击使用plus.runtime跳转打开支付宝 //打开支付宝授权&#xff0c;在支付宝APP中授权后会在支付宝中跳转到你填写的h5地址//urls是授权地址可以后端拼接也可以前端写死 //以下是一个拼接示例&#xff0c;需修改app_id的值和redirect_uri的值即可 //app_id是商户的APPID&…

OpenDDS的Rtps_Udp传输协议可靠性QoS收发基本流程

OpenDDS中,实现了Rtps_Udp传输协议(非纯udp)的可靠性传输。传输的线程包括: 1)发送方线程主要线程和定时器 《1》应用线程 《2》网络异步发送线程 《3》Heartbeat定时器 《4》Nak_response定时器 2)接收方主要线程和定时器 《1》网络异步接收线程 《2》heartbeat_respons…

Ubuntu 16.04 通过deb包安装内核头文件

文章目录 前言通过deb包安装内核头文件 前言 Ubuntu16.04部分内核版本无法通过 apt-get install linux-headers-$(uname -r) 来进行安装&#xff1a; # cat /etc/lsb-release DISTRIB_IDUbuntu DISTRIB_RELEASE16.04 DISTRIB_CODENAMExenial DISTRIB_DESCRIPTION"Ubuntu…

汇编

汇编指令 随机数mov指令mov指令ldr指令&#xff08;伪指令&#xff09;add指令sub指令bic指令orr指令b指令cmp指令stmfd指令ldmfd指令import栈汇编指令的s后缀 随机数 1.如果某个数的数值范围是0~255之间&#xff0c;那么这个数一定是立即数&#xff1b; 2.把某个数展开成2进制…

昇腾 - AscendCL C++应用开发 推理部分 模型执行

昇腾 - AscendCL C应用开发 推理部分 模型执行 flyfish 准备模型执行的输入/输出数据结构 aclmdlDataset├── aclDataBuffer│ ├── buffer address│ └── buffer size└── aclDataBuffer├── buffer address└── buffer size...使用aclmdlDataset类型的数据…

网络工程师必备10个Linux网络命令,超好用

号主&#xff1a;老杨丨11年资深网络工程师&#xff0c;更多网工提升干货&#xff0c;请关注公众号&#xff1a;网络工程师俱乐部 下午好&#xff0c;我的网工朋友 掌握正确的工具对于提升工作效率的重要性不言而喻&#xff0c;尤其在网络工程领域&#xff0c;这一点更是至关重…

尚品汇-购物车列表、临时用户购物车与登录用户购物车合并实现(三十七)

目录&#xff1a; &#xff08;1&#xff09;功能—展示购物车列表 &#xff08;2&#xff09;在web-all添加前端实现 &#xff08;3&#xff09;功能--合并购物车 &#xff08;1&#xff09;功能—展示购物车列表 购物车列表接口&#xff1a;CartService /*** 通过用户Id …

买完服务器后,如何部署项目|如何通过宝塔部署项目

一、前言 很多人都会在腾讯云、阿里云等平台上买服务器&#xff0c;但是买了服务器后&#xff0c;不知道接下来要干什么&#xff0c;或者是怎么部署项目。 买完服务器后&#xff0c;第一步就是再买一个数据盘&#xff0c;然后将数据盘挂载到服务器上&#xff0c;不知道教程的…

Vue: v-html安全性问题

一、问题描述 可能都知道使用v-html插入富文本&#xff0c;存在安全隐患&#xff0c;比如 cross-site scripting attack&#xff08;xss&#xff09;。但具体什么情况下v-html会引发安全问题呢&#xff1f;是否内容中含有<scrpit>标签就会触发执行脚本呢&#xff1f; 二…

【Unity3D小技巧】Unity3D中实现对InputField的自定义输入限制实例

推荐阅读 CSDN主页GitHub开源地址Unity3D插件分享简书地址QQ群&#xff1a;398291828 大家好&#xff0c;我是佛系工程师☆恬静的小魔龙☆&#xff0c;不定时更新Unity开发技巧&#xff0c;觉得有用记得一键三连哦。 一、前言 InputField是UGUI的文本输入框&#xff0c;自带的…

eNSP的使用和下载安装

有小伙伴想考数通的HCIP的或有网络问题的可以进群讨论 找志同道合的人,一起讨论问题 很多小伙伴在安装eNSP的时候出现过很多问题,为了后续我们的专题能进行,这里提前教大家如何下载eNSP 大家可以参考这位大佬的安装,非常的详细 华为 eNSP 模拟器安装教程&#xff08;内含下载地…