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

学习 Pinia 状态管理【Plan - May - Week 2】

一、定义 Store

Store 由 defineStore() 定义,它的第一个参数要求独一无二的id

import { defineStore } from 'pinia'export const useAlertsStore = defineStore('alert', {// 配置
})
  • 最好使用以 use 开头且以 Store 结尾 (比如 useUserStoreuseCartStoreuseProductStore) 来命名 defineStore() 的返回值
  • Pinia 将用传入的 id 来连接 store 和 devtools
  • defineStore() 的第二个参数可接受两类值:Setup 函数或 Option 对象。

1、Option Store

与 Vue 的选项式 API 类似,可以传入一个带有 stateactionsgetters 属性的 Option 对象

export const useCounterStore = defineStore('counter', {
state: () => ({ count: 0, name: 'Tomato' }),
getters: {doubleCount: (state) => state.count * 2,
},
actions: {increment() {this.count++},
},
})
  • state 相当于 store 的数据,getters 相当于 store 的计算属性,action 相当于 store 的方法

2、Setup Store

Setup 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 }
})
  • ref() 相当于 state 属性
  • computed() 相当于 getters
  • function() 相当于 actions
  • 要让 pinia 正确识别 state必须在 setup store 中返回 state 的所有属性。因此不能在 store 中使用私有属性。
  • 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 {// ...}
})

3、使用 Store

store 实例需要像使用 <script setup> 调用 useStore() 才会被创建

<script setup>
import { useCounterStore } from '@/stores/counter'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 // 将始终是 "Tomato"
doubleCount // 将始终是 0
setTimeout(() => {store.increment()
}, 1000)
// 这样写是响应式的,也可以直接使用 `store.doubleCount`
const doubleValue = computed(() => store.doubleCount)
</script>

4、从 Store 解构

使用 storeToRefs() 保持属性从 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>

二、Pinia

1、使用 Pinia

使用 Pinia可以获得以下功能:

  • 测试工具集
  • 插件:可通过插件扩展 Pinia 功能
  • 为 JS 开发者提供适当的 TypeScript 支持以及自动补全功能。
  • 支持服务端渲染
  • Devtools 支持
    • 追踪 actions、mutations 的时间线
    • 在组件中展示它们所用到的 Store
    • 让调试更容易的 Time travel
  • 热更新
    • 不必重载页面即可修改 Store
    • 开发时可保持当前的 State

2、基础示例

创建 Store

// stores/counter.js
import { defineStore } from 'pinia'export const useCounterStore = defineStore('counter', {
state: () => {return { count: 0 }
},
// 也可以这样定义
// state: () => ({ count: 0 })
actions: {increment() {this.count++},
},
})

使用 store

<script setup>
import { useCounterStore } from '@/stores/counter'const counter = useCounterStore()counter.count++
// 自动补全!
counter.$patch({ count: counter.count + 1 })
// 或使用 action 代替
counter.increment()
</script><template>
<!-- 直接从 store 中访问 state -->
<div>Current Count: {{ counter.count }}</div>
</template>
  • 使用函数定义 Store
export const useCounterStore = defineStore('counter', () => {const count = ref(0)function increment() {count.value++}return { count, increment }
})

3、对比 Vuex

  • Pinia 已经实现了我们在 Vuex 5 中想要的大部分功能
  • 与 Vuex 相比,Pinia 不仅提供了一个更简单的 API,也提供了符合组合式 API 风格的 API,最重要的是,搭配 TypeScript 一起使用时有非常可靠的类型推断支持
  • Pinia 同样要经过 RFC 流程,并且其 API 也已经进入稳定状态
  • 无需要创建自定义的复杂包装器来支持 TypeScript,一切都可标注类型,API 的设计方式是尽可能地利用 TS 类型推理
  • 无过多的魔法字符串注入,只需要导入函数并调用它们
  • 无需要动态添加 Store,它们默认都是动态的
  • ……

三、Action

Action 相当于组件中的 method。它们可以通过 defineStore() 中的 actions 属性来定义

export const useCounterStore = defineStore('main', {
state: () => ({count: 0,
}),
actions: {increment() {this.count++},randomizeCounter() {this.count = Math.round(100 * Math.random())},
},
})
  • 类似 getter,action 也可通过 this 访问整个 store 实例
  • action 可以是异步的
export const useUsers = defineStore('users', {state: () => ({userData: null,// ...}),actions: {async registerUser(login, password) {try {this.userData = await api.post({ login, password })showTooltip(`Welcome back ${this.userData.name}!`)} catch (error) {showTooltip(error)// 让表单组件显示错误return error}},},
})
  • 调用
<script setup>
const store = useCounterStore()
// 将 action 作为 store 的方法进行调用
store.randomizeCounter()
</script>
<template><!-- 即使在模板中也可以 --><button @click="store.randomizeCounter()">Randomize</button>
</template>

1、访问其他 store 的 action

直接调用

import { useAuthStore } from './auth-store'export const useSettingsStore = defineStore('settings', {
state: () => ({preferences: null,// ...
}),
actions: {async fetchUserPreferences() {const auth = useAuthStore()if (auth.isAuthenticated) {this.preferences = await fetchPreferences()} else {throw new Error('User must be authenticated')}},
},
})

2、使用选项式 API 的用法

使用 setup()

<script>
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)},
},
})
</script>

3、订阅 action

通过 store.$onAction() 来监听 action 和它们的结果

  • 传递给它的回调函数会在 action 本身之前执行
  • after 表示在 promise 解决之后,允许你在 action 解决后执行一个回调函数
  • onError 允许你在 action 抛出错误或 reject 时执行一个回调函数
const unsubscribe = someStore.$onAction(({name, // action 名称store, // store 实例,类似 `someStore`args, // 传递给 action 的参数数组after, // 在 action 返回或解决后的钩子onError, // action 抛出或拒绝的钩子}) => {// 为这个特定的 action 调用提供一个共享变量const startTime = Date.now()// 这将在执行 "store "的 action 之前触发。console.log(`Start "${name}" with params [${args.join(', ')}].`)// 这将在 action 成功并完全运行后触发。// 它等待着任何返回的 promiseafter((result) => {console.log(`Finished "${name}" after ${Date.now() - startTime}ms.\nResult: ${result}.`)})// 如果 action 抛出或返回一个拒绝的 promise,这将触发onError((error) => {console.warn(`Failed "${name}" after ${Date.now() - startTime}ms.\nError: ${error}.`)})}
)// 手动删除监听器
unsubscribe()
  • action 会绑定在 store 组件的 setup() 内,当组件被卸载时,它们将被自动删除
  • 可以将 true 作为第二个参数传递给 action 订阅器,可以实现即便在组件卸载之后仍会被保留
<script setup>
const someStore = useSomeStore()
// 此订阅器即便在组件卸载之后仍会被保留
someStore.$onAction(callback, true)
</script>

四、插件

1、简介

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

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

2、扩展 Store

  • 直接通过在一个插件中返回包含特定属性的对象来为每个 store 都添加上特定属性
pinia.use(() => ({ hello: 'world' }))

或者

  • 直接在 store 上设置该属性(建议使用返回对象的方法,这样它们就能被 devtools 自动追踪到)
pinia.use(({ store }) => {store.hello = 'world'// 确保你的构建工具能处理这个问题,webpack 和 vite 在默认情况下应该能处理。if (process.env.NODE_ENV === 'development') {// 添加你在 store 中设置的键值store._customProperties.add('hello')}
})

每个 store 都被 reactive包装过,所以可以自动解包任何它所包含的 Ref(ref()computed()…)

const sharedRef = ref('shared')
pinia.use(({ store }) => {
// 每个 store 都有单独的 `hello` 属性
store.hello = ref('secret')
// 它会被自动解包
store.hello // 'secret'// 所有的 store 都在共享 `shared` 属性的值
store.shared = sharedRef
store.shared // 'shared'
})

添加新的 state

如果给 store 添加新的 state 属性或者在服务端渲染的激活过程中使用的属性,必须同时在两个地方添加。

  • store
  • store.$state
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

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`) 设置为 store
const originalReset = store.$reset.bind(store)// 覆写其 $reset 函数
return {$reset() {originalReset()store.hasError = false},
}
})

3、添加新的外部属性

当添加外部属性、第三方库的类实例或非响应式的简单值时,需要先用 markRaw() 进行包装后再传给 pinia

import { markRaw } from 'vue'
// 根据你的路由器的位置来调整
import { router } from './router'pinia.use(({ store }) => {
store.router = markRaw(router)
})

4、添加新的选项

在定义 store 时,可以创建新的选项,以便在插件中使用它们。例如,你可以创建一个 debounce 选项,允许你让任何 action 实现防抖。

defineStore('search', {
actions: {searchContacts() {// ...},
},// 这将在后面被一个插件读取
debounce: {// 让 action searchContacts 防抖 300mssearchContacts: 300,
},
})
// 使用任意防抖库
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}, {})
}
})

学习资料来源:

定义 Store | Pinia
简介 | Pinia
Action | Pinia
插件 | Pinia

http://www.xdnf.cn/news/573391.html

相关文章:

  • 创建一个element plus项目
  • [C++入门]类和对象下
  • 东莞一锂离子电池公司IPO终止,客户与供应商重叠,社保缴纳情况引疑
  • GitLab 配置 webhook
  • 越小越优先和越大越优先
  • oracle使用SPM控制执行计划
  • 使用Redis的Bitmap实现了签到功能
  • iPaaS集成平台技术选型关注哪些指标?
  • HJ20 密码验证合格程序【牛客网】
  • 测试W5500的第4步_使用ioLibrary库创建UDP客户端和服务器端
  • 数据结构核心知识总结:从基础到应用
  • 6-码蹄集600题基础python篇
  • Mysql数据库相关命令及操作
  • 链表-两两交换链表中的节点
  • Mysql差异备份与恢复
  • Python图像处理全攻略:从基础到前沿技术深度剖析
  • 极大似然估计与机器学习
  • python查询elasticsearch 获取指定字段的值的list
  • 操作系统期末复习(一)
  • 淘宝扭蛋机小程序开发:开启电商娱乐新玩法
  • 工程项目交付质量低?如何构建标准化管理体系?
  • C++网络编程入门学习(四)-- GDB 调试 学习 笔记
  • 第9.1讲、Tiny Encoder Transformer:极简文本分类与注意力可视化实战
  • 计算机操作系统(十)调度的概念与层次,进程调度的时机与进程的调度方式
  • LVLM-AFAH论文精读
  • GitHub SSH Key 配置详细教程(适合初学者,Windows版)-学习记录4
  • CESM2.0 全流程解析:从环境搭建到多模块耦合模拟
  • 打开小程序提示请求失败(小程序页面空白)
  • Python实现蛋白质结构RMSD计算
  • RAG 挑战赛冠军方案解析:从数据解析到多路由器检索的工程实践,推荐阅读!