Vue全家桶 - pinia 的理解和学习2(Pinia 核心概念的插件、组件外的 Store 和 服务器渲染(SSR))

Pinia(Vue 的专属状态管理库)

Vue全家桶 - pinia 的理解和学习1(Pinia 核心概念的 Store、State、Getter、Action) https://blog.csdn.net/weixin_54092687/article/details/140520675

插件

由于有了底层 API 的支持,Pinia store 现在完全支持扩展。以下是你可以扩展的内容:
为 store 添加新的属性
定义 store 时增加新的选项
为 store 增加新的方法
包装现有的方法
改变甚至取消 action
实现副作用,如本地存储
仅应用插件于特定 store

// 通过 pinia.use() 添加插件到 pinia 实例的。
import { createPinia } from 'pinia'
function SecretPiniaPlugin() { return { secret: 'the cake is a lie' } }
const pinia = createPinia()
pinia.use(SecretPiniaPlugin)// 在组件中使用
const store = useStore()
store.secret // 'the cake is a lie'

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

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

扩展 Store

// 方式1 -- 可以直接通过在一个插件中返回包含特定属性的对象来为每个 store 都添加上特定属性。
pinia.use(() => ({ hello: 'world' }))
// 方式2 -- 可以直接在 store 上设置该属性,最好使用返回对象的方法。
pinia.use(({ store }) => { store.hello = 'world' })// 想在 devtools 中调试 hello 属性,为了使 devtools 能追踪到 hello。
pinia.use(({ store }) => {store.hello = 'world'if (process.env.NODE_ENV === 'development') {store._customProperties.add('hello') // 添加你在 store 中设置的键值}
})// 每个 store 都被 reactive 包装过,所以可以自动解包任何它所包含的 Ref(ref()、computed()...)。
const sharedRef = ref('shared')
pinia.use(({ store }) => {store.hello = ref('secret') // 每个 store 都有单独的 `hello` 属性,它会被自动解包store.hello // 'secret'store.shared = sharedRef // 所有的 store 都在共享 `shared` 属性的值store.shared // 'shared'
})
添加新的 state

给 store 添加新的 state 属性或者在服务端渲染的激活过程中使用的属性,必须同时在两个地方添加它。
在 store 上,然后才可以用 store.myState 访问它。
在 store.$state 上,然后才可以在 devtools 中使用它,并且,在 SSR 时被正确序列化(serialized)。
除此之外,你肯定也会使用 ref()(或其他响应式 API),以便在不同的读取中共享相同的值。

import { toRef, ref } from 'vue'pinia.use(({ store }) => {// 为了正确地处理 SSR,我们需要确保我们没有重写任何一个现有的值if (!store.$state.hasOwnProperty('hasError')) {// 在插件中定义 hasError,因此每个 store 都有各自的 hasError 状态const hasError = ref(false)// 在 `$state` 上设置变量,允许它在 SSR 期间被序列化。store.$state.hasError = hasError}// 我们需要将 ref 从 state 转移到 store// 这样的话,两种方式:store.hasError 和 store.$state.hasError 都可以访问// 并且共享的是同一个变量// 查看 https://cn.vuejs.org/api/reactivity-utilities.html#torefstore.hasError = toRef(store.$state, 'hasError')// 在这种情况下,最好不要返回 `hasError`// 因为它将被显示在 devtools 的 `state` 部分// 如果我们返回它,devtools 将显示两次。
})

需要注意的是,在一个插件中, state 变更或添加(包括调用 store.$patch())都是发生在 store 被激活之前,因此不会触发任何订阅函数。

重置插件中添加的 state

默认情况下,$reset() 不会重置插件添加的 state,但你可以重写它来重置你添加的 state。

import { toRef, ref } from 'vue'
pinia.use(({ store }) => {if (!store.$state.hasOwnProperty('hasError')) {const hasError = ref(false)store.$state.hasError = hasError}store.hasError = toRef(store.$state, 'hasError')// 确认将上下文 (`this`) 设置为 storeconst originalReset = store.$reset.bind(store)// 覆写其 $reset 函数return {$reset() {originalReset()store.hasError = false}}
})

添加新的外部属性

当添加外部属性、第三方库的类实例或非响应式的简单值时,你应该先用 markRaw() 来包装一下它,再将它传给 pinia。

import { markRaw } from 'vue'
import { router } from './router'
pinia.use(({ store }) => { store.router = markRaw(router) })

在插件中调用 $subscribe

pinia.use(({ store }) => {store.$subscribe(() => { }) // 响应 store 变化store.$onAction(() => { }) // 响应 store actions
})

添加新的选项

在定义 store 时,可以创建新的选项,以便在插件中使用它们。

// 创建一个 debounce 选项,允许让任何 action 实现防抖。
defineStore('search', {actions: {searchContacts() { }},// 这将在后面被一个插件读取debounce: {searchContacts: 300 // 让 action searchContacts 防抖 300ms}
})// 然后,该插件可以读取该选项来包装 action,并替换原始 action
import debounce from 'lodash/debounce' // 使用任意防抖库
pinia.use(({ options, store }) => {if (options.debounce) {// 我们正在用新的 action 来覆盖这些 actionreturn Object.keys(options.debounce).reduce((debouncedActions, action) => {debouncedActions[action] = debounce(store[action],options.debounce[action])return debouncedActions}, {})}
})// 注意,在使用 setup 语法时,自定义选项作为第 3 个参数传递:
defineStore( 'search',  () => { }, {debounce: { searchContacts: 300 }}
)

TypeScript

上述一切功能都有类型支持,所以你永远不需要使用 any 或 @ts-ignore。

标注插件类型
// 一个 Pinia 插件可按如下方式实现类型标注:
import { PiniaPluginContext } from 'pinia'
export function myPiniaPlugin(context: PiniaPluginContext) { }
为新的 store 属性添加类型
// 当在 store 中添加新的属性时,你也应该扩展 PiniaCustomProperties 接口。
import 'pinia'
declare module 'pinia' {export interface PiniaCustomProperties {set hello(value: string | Ref<string>) // 通过使用一个 setter,我们可以允许字符串和引用。get hello(): stringsimpleNumber: number // 你也可以定义更简单的值router: Router // 你也可以定义更简单的值}
}// 然后,就可以安全地写入和读取它了:
pinia.use(({ store }) => {store.hello = 'Hola'store.hello = ref('Hola')store.simpleNumber = Math.random()// @ts-expect-error: we haven't typed this correctlystore.simpleNumber = ref(Math.random())
})

PiniaCustomProperties 是一个通用类型,允许你引用 store 的属性。
当在泛型中扩展类型时,它们的名字必须与源代码中完全一样。
S: State | G: Getters | A: Actions | SS: Setup Store / Store

// 如果把初始选项复制成 $options(这只对 option store 有效),如何标注类型:
pinia.use(({ options }) => ({ $options: options }))
// 可以通过使用 PiniaCustomProperties 的4种通用类型来标注类型:
import 'pinia'
declare module 'pinia' {export interface PiniaCustomProperties<Id, S, G, A> {$options: {id: Idstate?: () => Sgetters?: Gactions?: A}}
}
为新的 state 添加类型

当添加新的 state 属性(包括 store 和 store.$state )时,你需要将类型添加到 PiniaCustomStateProperties 中。与 PiniaCustomProperties 不同的是,它只接收 State 泛型。

import 'pinia'
declare module 'pinia' {export interface PiniaCustomStateProperties<S> {hello: string}
}
为新的定义选项添加类型

当为 defineStore() 创建新选项时,你应该扩展 DefineStoreOptionsBase。与 PiniaCustomProperties 不同的是,它只暴露了两个泛型:State 和 Store 类型,允许你限制定义选项的可用类型。

import 'pinia'
declare module 'pinia' {export interface DefineStoreOptionsBase<S, Store> {// 任意 action 都允许定义一个防抖的毫秒数debounce?: Partial<Record<keyof StoreActions<Store>, number>>}
}

在组件外使用 store

Pinia store 依靠 pinia 实例在所有调用中共享同一个 store 实例。大多数时候,只需调用你定义的 useStore() 函数,完全开箱即用。例如,在 setup() 中,你不需要再做任何事情。但在组件之外,情况就有点不同了。 实际上,useStore() 给你的 app 自动注入了 pinia 实例。这意味着,如果 pinia 实例不能自动注入,你必须手动提供给 useStore() 函数。 你可以根据不同的应用,以不同的方式解决这个问题。

单页面应用

如果你不做任何 SSR(服务器端渲染),在用 app.use(pinia) 安装 pinia 插件后,对 useStore() 的任何调用都会正常执行:

import { useUserStore } from '@/stores/user'
import { createPinia } from 'pinia'
import { createApp } from 'vue'
import App from './App.vue'// ❌  失败,因为它是在创建 pinia 之前被调用的
const userStore = useUserStore()const pinia = createPinia()
const app = createApp(App)
app.use(pinia)// ✅ 成功,因为 pinia 实例现在激活了
const userStore = useUserStore()

为确保 pinia 实例被激活,最简单的方法就是将 useStore() 的调用放在 pinia 安装后才会执行的函数中。

// 在 Vue Router 的导航守卫中使用 store 的例子。
import { createRouter } from 'vue-router'
const router = createRouter({// ...
})// ❌ 由于引入顺序的问题,这将失败
const store = useStore()router.beforeEach((to, from, next) => {// 我们想要在这里使用 storeif (store.isLoggedIn) next()else next('/login')
})router.beforeEach((to) => {// ✅ 这样做是可行的,因为路由器是在其被安装之后开始导航的,// 而此时 Pinia 也已经被安装。const store = useStore()if (to.meta.requiresAuth && !store.isLoggedIn) return '/login'
})

服务端渲染应用

当处理服务端渲染时,你将必须把 pinia 实例传递给 useStore()。这可以防止 pinia 在不同的应用实例之间共享全局状态。
只要你只在 setup 函数、getter 和 action 的顶部调用你定义的 useStore() 函数,那么使用 Pinia 创建 store 对于 SSR 来说应该是开箱即用的:

<script setup>
// 这是可行的,
// 因为 pinia 知道在 `setup` 中运行的是什么程序。const main = useMainStore()
</script>
在 setup() 外部使用 store

如果你需要在其他地方使用 store,你需要将原本被传递给应用 的 pinia 实例传递给 useStore() 函数:

const pinia = createPinia()
const app = createApp(App)app.use(router)
app.use(pinia)router.beforeEach((to) => {// ✅这会正常工作,因为它确保了正确的 store 被用于// 当前正在运行的应用const main = useMainStore(pinia)if (to.meta.requiresAuth && !main.isLoggedIn) return '/login'
})

Pinia 会将自己作为 $pinia 添加到你的应用中,所以你可以在 serverPrefetch() 等函数中使用它。

export default {serverPrefetch() {const store = useStore(this.$pinia)},
}
State 激活

为了激活初始 state,你需要确保 rootState 包含在 HTML 中的某个地方,以便 Pinia 稍后能够接收到它。根据你服务端所渲染的内容,为了安全你应该转义 state。

import devalue from '@nuxt/devalue'
import { createPinia } from 'pinia'
// 检索服务端的 rootState
const pinia = createPinia()
const app = createApp(App)
app.use(router)
app.use(pinia)// 渲染页面后,rootState 被建立,
// 可以直接在 `pinia.state.value`上读取。// 序列化,转义(如果 state 的内容可以被用户改变,这点就非常重要,几乎都是这样的)
// 并将其放置在页面的某处
// 例如,作为一个全局变量。
devalue(pinia.state.value)

根据你服务端所渲染的内容,你将设置一个初始状态变量,该变量将在 HTML 中被序列化。你还应该保护自己免受 XSS 攻击。

// 在 vite-ssr中你可以使用transformState 选项 以及 @nuxt/devalue:
import devalue from '@nuxt/devalue'
export default viteSSR(App,{routes,transformState(state) {return import.meta.env.SSR ? devalue(state) : state},},({ initialState }) => {// ...if (import.meta.env.SSR) {// 序列化并设置为 window.__INITIAL_STATE__initialState.pinia = pinia.state.value} else {// 在客户端,我们恢复 statepinia.state.value = initialState.pinia}}
)

你可以根据你的需要使用 @nuxt/devalue 的其他替代品,例如,你也可以用 JSON.stringify()/JSON.parse() 来序列化和解析你的 state,这样你可以把性能提高很多。
也可以根据你的环境调整这个策略。但确保在客户端调用任何 useStore() 函数之前,激活 pinia 的 state。例如,如果我们将 state 序列化为一个 <script> 标签,并在客户端通过 window.__pinia 全局访问它,我们可以这样写:

const pinia = createPinia()
const app = createApp(App)
app.use(pinia)// 必须由用户设置
if (isClient) {pinia.state.value = JSON.parse(window.__pinia)
}

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

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

相关文章

gradle 构建项目添加版本信息

gradle 构建项目添加版本信息 build.gradle 配置文件 bootJar {manifest {attributes(Project-Name: project.name,Project-Version: project.version,"project-Vendor": "XXX Corp","Built-By": "Gradle ${gradle.gradleVersion}",&…

DETR目标检测模型训练自己的数据集

前言 基础环境&#xff1a;ubuntu20.04、python3.8、pytorch:1.10.0、CUDA:11.3 代码地址&#xff1a;https://github.com/facebookresearch/detr 目录 一、训练准备1、预训练模型下载2、txt文件转为coco模式 二、修改训练模型参数三、开始训练四、实现DETR的推理 一、训练准备…

【RT摩拳擦掌】RT600 4路音频同步输入1路TDM输出方案

【RT摩拳擦掌】RT600 4路音频同步输入1路TDM输出方案 一&#xff0c; 文章简介二&#xff0c;硬件平台构建2.1 音频源板2.2 音频收发板2.3 双板硬件连接 三&#xff0c;软件方案与软件实现3.1 方案实现3.2 软件代码实现3.2.1 4路I2S接收3.2.2 I2S DMA pingpong配置3.2.3 音频数…

【大数据专题】数据分析

1. 大学内的各年纪人数分别为&#xff1a;一年级200&#xff0c;二年级160&#xff0c;三年级130&#xff0c;四年级110人。则年级属性的众数是 &#xff1f; A&#xff1a;一年级 B&#xff1a;二年级 C&#xff1a;三年级 D&#xff1a;四年级 A 2. 简述下面哪个属于映射数…

Python自动化批量下载ECWMF和GFS最新预报数据脚本

一、白嫖EC和GFS预报数据 EC的openData部分公开了一部分预报数据&#xff0c;作为普通用户只能访问这些免费预报数据&#xff0c;具体位置在这 可以发现&#xff0c;由于是Open Data&#xff0c;我们只能获得临近四天的预报结果&#xff0c;虽然时间较短&#xff0c;但是我们…

vue3前端开发-小兔鲜项目-二级页面面包屑导航和跳转

vue3前端开发-小兔鲜项目-二级页面面包屑导航和跳转&#xff01;这一次&#xff0c;做两件事。第一件事是把二级分类页面的跳转&#xff08;也就是路由&#xff09;设计一下。第二件事是把二级页面的面包屑导航设计一下。 第一件事&#xff0c;二级页面的跳转路由设计一下。 如…

Python爬虫(4) --爬取网页图片

文章目录 爬虫爬取图片指定url发送请求获取想要的数据数据解析定位想要内容的位置存放图片 完整代码实现总结 爬虫 Python 爬虫是一种自动化工具&#xff0c;用于从互联网上抓取网页数据并提取有用的信息。Python 因其简洁的语法和丰富的库支持&#xff08;如 requests、Beaut…

科普文:后端性能优化的实战小结

一、背景与效果 ICBU的核心沟通场景有了10年的“积累”&#xff0c;核心场景的界面响应耗时被拉的越来越长&#xff0c;也让性能优化工作提上了日程&#xff0c;先说结论&#xff0c;经过这一波前后端齐心协力的优化努力&#xff0c;两个核心界面90分位的数据&#xff0c;FCP平…

Day05-readinessProbe探针,startupProbe探针,Pod生命周期,静态Pod,初始化容器,rc控制器的升级和回滚,rs控制器精讲

Day05-readinessProbe探针&#xff0c;startupProbe探针&#xff0c;Pod生命周期&#xff0c;静态Pod&#xff0c;初始化容器&#xff0c;rc控制器的升级和回滚&#xff0c;rs控制器精讲 0、昨日内容回顾1、readinessProbe可用性检查探针之exec案例2、可用性检查之httpGet案例3…

[数据集][目标检测]躺坐站识别检测数据集VOC+YOLO格式9488张3类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;9488 标注数量(xml文件个数)&#xff1a;9488 标注数量(txt文件个数)&#xff1a;9488 标注…

「Vue组件化」封装SVG图标

Vue组件化,封装SVG图标。支持uniapp等vue版本。 前言 设计思路 创建SVG组件: 首先创建一个用于加载SVG文件的Vue组件。 SVG文件组织: 将SVG文件放在项目的某个目录下,例如components/svgIcon/。 SVG组件注册: 创建一个函数来动态导入SVG文件,并注册到Vue组件中。 使用S…

Python每日学习

我是从c转来学习Python的&#xff0c;总感觉和c相比Python的实操简单&#xff0c;但是由于写c的代码多了&#xff0c;感觉Python的语法好奇怪 就比如说c的开头要有库&#xff08;就是类似于#include <bits/stdc.h>&#xff09;而且它每一项的代码结束之后要有一个表示结…

C语言 | Leetcode C语言题解之第242题有效的字母异位词

题目&#xff1a; 题解&#xff1a; bool isAnagram(char* s, char* t) {int len_s strlen(s), len_t strlen(t);if (len_s ! len_t) {return false;}int table[26];memset(table, 0, sizeof(table));for (int i 0; i < len_s; i) {table[s[i] - a];}for (int i 0; i &…

EMQX 跨域集群:增强可扩展性,打破地域限制

跨域集群的概念 提到 EMQX&#xff0c;人们通常首先会想到它的可扩展性。尽管 EMQX 能随着硬件数量的增加几乎实现线性扩展&#xff0c;但在单个计算实例上的扩展能力终究有限&#xff1a;资源总会耗尽&#xff0c;升级成本也会急剧上升。这时&#xff0c;分布式部署就显得尤为…

HOW - CSS 定义颜色值

目录 1. 十六进制颜色 (Hexadecimal Color)2. RGB 颜色 (RGB Color)3. HSL 颜色 (HSL Color)HSL 颜色模式示例 4. 预定义颜色名 (Named Colors)5. LCH 颜色 (LCH Color)6. Lab 颜色 (Lab Color)7. HWB 颜色 (HWB Color)8. CSS 颜色函数 (Color Function)9. CSS4 颜色模块中的其…

JavaScript(11)——对象

对象 声明&#xff1a; let 对象名 { 属性名&#xff1a;属性值, 方法名&#xff1a;函数 } let 对象名 new Object() 对象的操作 先创建一个对象 let op {name:jvav,id:4,num:1001} 查 对象名.属性 console.log(op.name) 对象名[属性名] 改 对象名.属性 新值 op.name …

Pytorch学习笔记day4——训练mnist数据集和初步研读

该来的还是来了hhhhhhhhhh&#xff0c;基本上机器学习的初学者都躲不开这个例子。开源&#xff0c;数据质量高&#xff0c;数据尺寸整齐&#xff0c;问题简单&#xff0c;实在太适合初学者食用了。 今天把代码跑通&#xff0c;趁着周末好好的琢磨一下里面的各种细节。 代码实…

Spring MVC的高级功能——拦截器(三)拦截器的执行流程

一、单个拦截器的执行流程 如果在项目中只定义了一个拦截器&#xff0c;单个拦截器的执行流程如图所示。 二、单个拦截器的执行流程分析 从单个拦截器的执行流程图中可以看出&#xff0c;程序收到请求后&#xff0c;首先会执行拦截器中的preHandle()方法&#xff0c;如果preHa…

bug诞生记——动态库加载错乱导致程序执行异常

大纲 背景问题发生问题猜测和分析过程是不是编译了本工程中的其他代码是不是有缓存是不是编译了非本工程的文件是不是调用了其他可执行文件查看CMakefiles分析源码检查正在运行程序的动态库 解决方案 这个案例发生在我研究ROS 2的测试Demo时发生的。 整体现象是&#xff1a;修改…

聊一聊前端动画的种类,以及动画的触发方式有哪些?

引言 动画在前端开发中扮演着重要的角色。它不仅可以提升用户体验&#xff0c;还可以使界面更加生动和有趣。在这篇文章中&#xff0c;我们将深入探讨前端动画的各种实现方式&#xff0c;包括 CSS 动画、JavaScript 动画、SVG 动画等。我们还将讨论一些触发动画的方式和动画在…