vue3中如何使用pinia -- pinia使用教程(一)

vue3中如何使用pinia -- pinia使用教程(一)

    • 安装
    • 使用
    • 创建 store
    • 使用 store
      • 访问
      • 修改 store
    • 使用组合式 api 创建 store -- setup store
    • pinia 和 hook 的完美结合
      • 如何解决上面的问题
    • 使用 hook 管理全局状态和 pinia 有何优缺点?
    • 参考
    • 小结

pinia 是一个 Vue3 的状态管理库,它的 API 设计和 Vuex 有很大的相似之处,但是它的实现方式和 Vuex 完全不同,它是基于 Vue3 的新特性 Composition API 实现的,所以它的使用方式和 Vuex 也有很大的不同。

安装

npm i pinia

使用

main.js

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

在使用 store 之前,需要先注册 pinia 插件。

import {useUserStore
} from '@/stores/user'
import {createPinia
} from 'pinia'
import {createApp
} from 'vue'
import App from './App.vue'// ❌  fails because it's called before the pinia is created
const userStore = useUserStore()const pinia = createPinia()
const app = createApp(App)
app.use(pinia)// ✅ works because the pinia instance is now active
const userStore = useUserStore()// more info https://pinia.vuejs.org/core-concepts/outside-component-usage.html

创建 store

import {defineStore
} from 'pinia'// 定义并导出容器
// 参数1:store id
// 参数2:选项对象
export const useCounter = defineStore('counter', {/*** 全局状态:使用箭头函数返回*/state: () => {return {count: 100,age: 20,books: ['vue', 'react', 'svelte'],}},getters: {// NOTE getters 使用了 this,需要手动声明返回值类型booksStr(state): string {console.log(state.books)return this.books.join('--')},},actions: {complexChange(step: number) {this.age += stepthis.books.push('solidjs', 'lit')},},
})

四个注意点:

  1. state 使用函数返回一个状态;
  2. getters 使用了 this,需要手动声明返回值类型;
  3. actions 使用 this 访问状态和 getters。actions可以是异步的,不再有 mutations;
  4. getters 和 actions 不使用箭头函数,否则 this 会指向 window,而不是 state。
  5. 每个 store 只注册一次,即 id 不能重复。

使用 store

<template><div><p>counter.count {{ counter.count }}</p><p>count{{ count }}</p><p>age:{{ age }}</p><ul><li v-for="book in books" :key="book">{{ book }}</li></ul><p>{{ booksStr }}</p><button @click="add">+</button><hr /><button @click="changeMulti">批量修改</button><hr /><button @click="changeMulti2">$patch使用接收函数</button></div>
</template><script setup lang="ts">import {storeToRefs} from 'pinia'import {useCounter,useTodosStore} from '@/stores'const {finishedTodos,todos} = storeToRefs(useTodosStore())// NOTE 不要直接解构,会失去响应式// const { count } = counterconst {count,age,books,booksStr} = useCounter()// const { count, age, books, booksStr } = storeToRefs(counter)// NOTE 状态修改// 方式1:最简单function add() {++counter.count}// 方式2:修改多个数据,使用 $patch 接收函数批量更新function changeMulti2() {counter.$patch(counter => {counter.count += 10counter.books.push('angular')})}// 方式3:修改多个数据,使用 $patch 批量修改function changeMulti() {counter.$patch({count: counter.count + 1,age: counter.age + 10,})}// 方式4:封装 actions,适合复杂操作function changeByAction() {counter.complexChange(10)}
</script>

打印整个 store,是一个 proxy 对象,counter 里声明的属性都能在里面看到,这些普通属性(数据)都是 ref。

vue3-pinia-store-counter

可以像使用普通 ref 一样使用 store 的数据 — 监听,用于计算属性等等。

const doubleCount = computed(() => {return counter.count * 2
})
watch(() => counter.count,count => {console.log(count, 'zqj log')}
)

访问

两种访问方式:

  1. 不解构,使用整个 store 对象
<template><p>{{ counter.count }}</p>
</template>
<script setup lang="ts">import {useCounter} from '@/stores'const counter = useCounter()// 接解构,会失去响应式// const { count, age, books, booksStr } = useCounter()// NOTE 状态修改// 方式1:最简单function add() {++counter.count}// 方式2:封装 actions,适合复杂操作function changeByAction() {counter.complexChange(10)}
</script>
  1. 解构,借助storeToRefs保持属性响应性
<template><p>{{ count }}</p>
</template><script setup lang="ts">import {useCounter} from '@/stores'const counter = useCounter()const {count,age,books,booksStr} = storeToRefs(counter)// 方式3:修改多个数据,使用 $patch 接收函数批量修改function changeMulti2() {counter.$patch(counter => {counter.count += 10counter.books.push('angular')})}// 方式4:修改多个数据,使用 $patch 接收对象批量修改function changeMulti() {counter.$patch({count: counter.count + 1,age: counter.age + 10,})}
</script>

修改 store

有 3 种方式

// 方式1:直接修改 store 里的属性
function add() {++counter.count
}
// 方式2:封装 actions,适合复杂操作
function changeByAction() {counter.complexChange(10)
}// 方式3:修改多个数据,使用 $patch 接收函数批量修改
function changeMulti2() {counter.$patch(counter => {counter.count += 10counter.books.push('angular')})
}

使用组合式 api 创建 store – setup store

上面的 useCounter 使用选项式 api 创建,pinia 也支持组合式 api, 这和 vue3 的组合式函数非常贴近,使用上更加简单。

defineStore 的第二个参数,可接收一个函数,该函数内部可使用 refcomputedwatch 等 vue 的组合式函数。

import {defineStore
} from 'pinia'export const useTodosStore = defineStore('todos', () => {const todos = reactive([{id: '1',finished: true,content: 'coding'},{id: '2',finished: false,content: 'eating'},])const finishedTodos = computed(() => {console.log('computed')return todos.filter(todo => todo.finished).map(todo => todo.content)})function finish(id: string, isFinished: boolean) {const index = todos.findIndex(todo => todo.id === id)todos[index].finished = isFinished}watch(todos, newTodos => {console.log(newTodos, 'newTodos')})function remove(id) {const index = todos.findIndex(todo => todo.id === id)todos.splice(index, 1)}return {todos,finish,remove,finishedTodos}
})

defineStore 的第二个参数,就是一个普通的组合式函数。

注意: 第二个参数虽然是一个函数,都是无法接收参数。

学习使用 hook 管理全局状态时,有如下 useCart 例子,用于记录购物车的商品信息。

import { readonly } from 'vue'export type Cart = {id: numbername: stringnumber: numberprice: number
}const items = ref<Cart[]>([])const totalBooks = computed(() =>items.value.reduce((preTotal, current) => {preTotal += current.numberreturn preTotal}, 0)
)export default function useCart() {function addCart(item) {const exist = items.value.find(el => el.id === item.id)if (exist) exist.number += 1else items.value.push({ id: item.id, name: item.name, number: 1, price: item.price })}function removeCart(id: number) {const index = items.value.findIndex(el => el.id === id)if (index !== -1) {const number = items.value[index].numbernumber === 1 && items.value.splice(index, 1)number >= 2 && (items.value[index].number -= 1)}}// NOTE 导出的 items 是内部的 items 的只读副本// 防止在外部意外更改状态return { items: readonly(items), totalBooks: readonly(totalBooks), addCart, removeCart }// return { items: items, totalBooks, addCart, removeCart }
}

在商品页添加到购物车:

<script setup lang="ts">import useCart from './useCart'const books = ref([{id: 1,name: 'vue',price: 12},{id: 2,name: 'react',price: 20},{id: 3,name: 'angular',price: 21},])const {addCart,removeCart} = useCart()
</script><template><div><h3>使用hook共享状态</h3><h4>书本列表</h4><ul><li v-for="(item, index) in books" :key="index"><button @click="() => removeCart(item.id)">-</button>{{ item.name }} -- ¥{{ item.price }}<button @click="() => addCart(item)">+</button></li></ul></div>
</template>

在购物车页面,显示购物车里的商品信息:

<script lang="ts" setup>import useCart from './useCart'const {items,totalBooks} = useCart()const totalPrice = computed(() => {return items.value.reduce((total, item) => {return total + item.price * item.number}, 0)})
</script><template><div class="user-cart"><h4>购物车</h4><ul><li v-for="(item, index) in items" :key="index">{{ item.name }}--{{ item.price }}¥ --- {{ item.number }}</li></ul><div>总共:{{ totalBooks }}本</div><div>总价:{{ totalPrice }}元</div></div>
</template>

一个简单的 hook,就实现了管理全局状态

pinia 和 hook 的完美结合

现在,使用 pinia 来接管这个功能。

import { defineStore } from 'pinia'import useCart from '@/components/HookTest/useCart'export const useCartStore = defineStore('cart', useCart)

商品页面,从 store 里导出的方法,模板保持不变。

<script setup lang="ts">// import useCart from './useCart'import {useCartStore} from '@/stores'const books = ref([{id: 1,name: 'vue',price: 12},{id: 2,name: 'react',price: 20},{id: 3,name: 'angular',price: 21},])const {addCart,removeCart} = useCartStore()// const { addCart, removeCart } = useCart()
</script>

购物车页面,可以从 store 里获取商品,也可以保持原来的代码不变。

从 store 里获取商品

CartDemo.vue

<script lang="ts" setup>import {useCartStore} from '@/stores'const userCart = useCartStore()const totalPrice = computed(() => {return userCart.items.reduce((total, item) => {return total + item.price * item.number}, 0)})
</script><template><div class="user-cart"><h4>购物车</h4><ul><li v-for="(item, index) in userCart.items" :key="index">{{ item.name }}--{{ item.price }}¥ --- {{ item.number }}</li></ul><div>总共:{{ userCart.totalBooks }}本</div><div>总价:{{ totalPrice }}元</div></div>
</template>

hook 和 pinia 结合得如此完美,如此方便,很美妙。

问题
useCart 把状态放在 hook 外部(变成全局变量),当和 pinia 结合时,可以放在内部吗?

可以。

这个特点非常棒,意味着不是用于 共享全局状态 的 hook即普通hook,不做任何改动也能方便地通过 pinia 实现共享全局状态。pinia 和 hook 和结合,没有侵入性。

一个 store 只会注册一次, CartDemo.vue 再次挂载,不会再次注册。要是二个参数是一个 hook, hooks 内的初始化操作不会再次执行,这个行为和 hooks 的行为不同,很可能导致 bug。

比如下面的代码:

function useTestHooks(type='hook') {const { adcd } = useUser() // 组件初始时,获取用户行政区console.log('useTestHooks', 'zqj log ',type)return {adcd,}
}
export {useTestHooks
}

下面的 useTestHooks 函数,每次组件挂载前,都会执行。

<script setup lang="ts">import {useTestHooks} from '@/hooks'const {adcd} = useTestHooks()
</script>

当把 useTestHooks 和 setup store 结合时,只会执行一次。

  import {useTestHooks} from '@/hooks';const useTestStore = defineStore('testStore', useTestHooks)export {useTestStore}

在组件中使用 TestStore.vue

<script setup lang="ts">import {useTestStore} from '@/stores'const {adcd} = useTestStore()
</script>

TestStore.vue 第一次挂载,执行 useTestStore,注册 id 为 testStore 的 store,后续组件更新,不会再次执行 useTestStore,adcd 就不会更新。

也不能传递 type 参数。

如何解决上面的问题

使用一个工厂函数,组件每次挂载,都会执行工厂函数,返回一个新的 store。

const createTestStore = (id = 'useTestStore', type) => {return defineStore(id, () => {return useTestHooks(type)})()
}

在组件中使用:

  import {createTestStore} from '@/stores'const {adcd} = createTestStore('newID', 'store')

每次组价挂载,给它传递一个新的 id,就新建一个全新的 store。

这种方案,也不好,要是组件挂载多次,就会创建多个 store,这个 store 也不会被销毁,但是可以手动销毁的 API。下个版本,会解决这个问题。

参考:

Passing arguments to useStore()

How to pass an argument to Pinia store?

Add ability to destroy stores

使用 hook 管理全局状态和 pinia 有何优缺点?

使用 hook 虽然能轻松管理全局状态,但是某些场景还是不如 pinia:

  1. hook 无法与 dev-tool 结合,意味着想要查看当前的状态不方便,不好调试;

  2. hook 扩展性不如 pinia:pinia 提供了插件扩展接口,能实现一些高级操作,比如统一订阅 store 的变化;

  3. 基于 1 原因,更加方便团队协作。

什么场景使用 hook 共享全局状态最适合?

在 5 个组件之间共享 3 个左右的状态最好。5 和 3 是经验得到的结论,总之不应大范围使用 hook 共享状态。

参考

Pinia: How to reset stores created with function/setup syntax

Vue 3 + Pinia - User Registration and Login Example & Tutorial

小结

pinia 和组合式 API 结合得非常好,项目里推荐使用这种方式。

hook 适合小范围共享状态。

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

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

相关文章

上位机图像处理和嵌入式模块部署(mcu之iap升级)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 mcu种类很多&#xff0c;如果是开发的时候需要对固件升级&#xff0c;整体还是比较容易的。不管是dap&#xff0c;还是st-link v2、j-link&#xf…

Modbus协议转Profibus协议网关接温控表与PLC通讯

一、前言 在智能化飞速发展的时代&#xff0c;各个行业都在使用自动化系统。在智能楼宇系统中&#xff0c;温控表和PLC&#xff08;可编程逻辑控制器&#xff09;通讯是至关重要的&#xff0c;在智能楼宇系统中&#xff0c;温控表起着监测和控制室内温度的重要作用。而PLC作为…

Elasticsearch中的Term_Filter过滤器技术

文章目录 一、引言二、Term Filter的工作原理与内部机制三、Term Filter的多样化使用场景3.1 精确匹配3.2 过滤分类与标签3.3 数据范围筛选3.4 复杂查询的构建 四、Term Filter的最佳实践与应用建议4.1 避免使用分析器4.2 优化索引映射4.3 充分利用缓存4.4 持续监控性能 五、结…

【地质灾害监测实现有效预警,44人提前安全转移】

6月13日14时&#xff0c;国信华源地质灾害监测预警系统提前精准预警&#xff0c;安全转移10户44人。 该滑坡隐患点通过科学部署国信华源裂缝计、倾角加速度计、雨量计、预警广播等自动化、智能化监测预警设备&#xff0c;实现了对隐患点裂缝、位移、降雨量等关键要素的实时动态…

PgSQL-添加列、字段的注释

mysql是&#xff1a; 添加列&#xff1a;--alter table 表名 add column 列名 varchar(30);ALTER TABLE p_show ADD COLUMN points VARCHAR(100) COMMENT 所需积分;---------------------------------------------------------------------------------------------添加、修改…

Jmeter性能 之 “查看结果树” 界面功能介绍

前言 查看结果树 显示所有请求响应的树&#xff0c;通过它可以查看任何请求的响应。除了显示响应之外&#xff0c;还可以查看获取响应所花费的时间以及一些响应代码。需要通过"查看结果树"来查看服务器处理请求之后的返回结果&#xff0c;分析是否存在问题 注意&am…

Typora v1.8.6解锁版安装教程 (轻便简洁的Markdown编辑器)

前言 Typora是一款轻便简洁的Markdown编辑器&#xff0c;支持即时渲染技术&#xff0c;这也是与其他Markdown编辑器最显著的区别。即时渲染使得你写Markdown就想是写Word文档一样流畅自如&#xff0c;不像其他编辑器的有编辑栏和显示栏。 一、下载地址 下载链接&#xff1a;…

游戏测试工程师面试,常问的问题有哪些?

一般会在面试中了解以下方面&#xff1a; 1.游戏热情&#xff0c;理解程度 玩过哪些游戏&#xff0c;这些游戏玩过多长时间&#xff0c;玩到什么样的水平&#xff0c;在游戏里花过多少钱 你觉得游戏里&#xff0c;xxx的设计如何&#xff0c;评价一下 2.编程、测试相关 学过哪…

任务3.8.3 利用RDD统计每日新增用户

任务目标 统计给定用户访问历史数据中&#xff0c;每日的新增用户数量。 数据准备 原始数据格式&#xff1a;每行包含两个字段&#xff0c;日期和用户名&#xff0c;以逗号分隔。示例数据&#xff1a;2024-05-01,mike 2024-05-01,alice 2024-05-01,brown ...解决方案 使用倒…

【2024.6.21】今日科技时事:科技前沿大事件

人不走空 &#x1f308;个人主页&#xff1a;人不走空 &#x1f496;系列专栏&#xff1a;算法专题 ⏰诗词歌赋&#xff1a;斯是陋室&#xff0c;惟吾德馨 目录 &#x1f308;个人主页&#xff1a;人不走空 &#x1f496;系列专栏&#xff1a;算法专题 ⏰诗词歌…

Notepad++插件 Hex-Edit

Nptepad有个Hex文件查看器&#xff0c;苦于每次打开文件需要手动开插件显示Hex&#xff0c;配置一下插件便可实现打开即调用 关联多个二进制文件&#xff0c;一打开就使用插件的方法&#xff0c;原来是使用空格分割&#xff01;&#xff01;&#xff01;

Java图形用户界面设计AWT事件处理

AWT事件处理 前言一、GUI事件处理机制定义使用步骤Swing事件处理机制与AWT的区别 二、GUI中常见事件和事件监听器事件低级事件高级事件 事件监听器AWT事件类的继承关系 三、事件适配器三、示例代码示例示例一示例二 示例三 前言 推荐一个网站给想要了解或者学习人工智能知识的…

Hi3861 OpenHarmony嵌入式应用入门--LiteOS Thread

目录 Thread API 主要接口说明 测试代码编写 代码分析 hi3861使用的实时系统主要是基于Huawei LiteOS-M&#xff0c;这是华为针对物联网领域推出的轻量级物联网操作系统内核。LiteOS-M是Huawei LiteOS的一个分支&#xff0c;专为IoT领域构建&#xff0c;主要面向没有MMU&am…

sqlmap使用以及GUI安装

下载 GUI版地址: GitHub - honmashironeko/sqlmap-gui: 基于官版本 SQLMAP 进行人工汉化&#xff0c;并提供GUI界面及多个自动化脚本 GUI使用 可以点击.bat启动 如果点击.bat启动不了就在这里打开cmd,输入对应的.bat来启动 linux安装 地址:sqlmap: automatic SQL injection…

记忆化搜索——AcWing 901. 滑雪

记忆化搜索 定义 记忆化搜索是一种结合了搜索和动态规划思想的方法。它通过将已经计算过的结果存储起来&#xff0c;在后续遇到相同情况时直接返回存储的结果&#xff0c;避免重复计算。 运用情况 当问题可以用递归方式求解&#xff0c;但存在大量重复计算时。一些复杂的组…

收藏||电商数据采集流程||电商数据采集API接口

商务数据分析的流程 第一步&#xff1a;明确分析目的。首先要明确分析目的&#xff0c;并把分析目的分解成若干个不同的分析要点&#xff0c;然后梳理分析思路&#xff0c;最后搭建分析框架。 第二步&#xff1a;数据采集。主流电商API接口数据采集&#xff0c;一般可以通过数…

顶顶通呼叫中心中间件-私有化asrproxy安装指南

一、安装asrproxy 上传asrproxy安装包到服务器目录&#xff1a;/root 上传完成之后依次执行下面的命令即可依次执行以下命令 cd ~mkdir -p /ddt/asrproxyunzip asrproxy_*.zip -d /ddt/asrproxycd /ddt/asrproxychmod x installlib.sh./installlib.shchmod x asrproxychmod x…

工控巨头去年业绩飙升10%,今年250亿出售子业务给美国黑石集团,意欲何为?...

导语 大家好&#xff0c;我是社长&#xff0c;老K。专注分享智能制造和智能仓储物流等内容。 新书《智能物流系统构成与技术实践》 更多的海量【智能制造】相关资料&#xff0c;请到智能制造online知识星球自行下载。 在工业自动化领域&#xff0c;艾默生一直以其卓越的技术和强…

CubeFS - 新一代云原生存储系统

CubeFS 是一种新一代云原生存储系统,支持 S3、HDFS 和 POSIX 等访问协议,支持多副本与纠删码两种存储引擎,为用户提供多租户、 多 AZ 部署以及跨区域复制等多种特性。 官方文档 CubeFS 作为一个云原生的分布式存储平台,提供了多种访问协议,因此其应用场景也非常广泛,下面…

驱动芯片退饱和保护(DESAT)

短路测试和双脉冲测试。 功率模块的短路承受能力的评估分为短路时间评估和短路能量评估两大类。短路时间由短路检测时间与短路关断时间共同构成 短路检测需要兼顾时效性与抗扰性能&#xff0c;要求系统能够及时响应&#xff0c;避免功率模块损坏。同时能够屏蔽开关过程的干扰…