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

news2024/10/7 15:26:03

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 += step
      this.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 } = counter
  const {
    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 += 10
      counter.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 += 10
      counter.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 += 10
    counter.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: number
  name: string
  number: number
  price: number
}

const items = ref<Cart[]>([])

const totalBooks = computed(() =>
  items.value.reduce((preTotal, current) => {
    preTotal += current.number
    return preTotal
  }, 0)
)

export default function useCart() {
  function addCart(item) {
    const exist = items.value.find(el => el.id === item.id)
    if (exist) exist.number += 1
    else 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].number
      number === 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.coloradmin.cn/o/1830561.html

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

相关文章

基于springboot+vue的供应商管理系统

一、系统架构 前端&#xff1a;vue2 | element-ui 后端&#xff1a;springboot | mybatis 环境&#xff1a;jdk1.8 | mysql | maven | node 二、代码及数据库 三、功能介绍 01. 员工注册 02. 登录 03. 管理员-首页 04. 管理员-个人中心-修改密码 05. …

CSP-J/S初赛01 计算机基础知识:计算机概述和计算机硬件系统

第1节 计算机概述 1.1 计算机的发展 代别 年代 逻辑&#xff08;电子&#xff09;元件 第一代 1946&#xff0d;1958 真空电子管 第二代 1959&#xff0d;1964 晶体管 第三代 1965&#xff0d;1970 集成电路 第四代 1971&#xff0d;至今 大规模、超大规模集成电…

华为 HarmonyOS 中国市场份额一季度超越苹果 iOS

华为 HarmonyOS 中国市场份额一季度超越苹果 iOS 根据最新发布的数据&#xff0c;研究机构Counterpoint Research指出&#xff0c;在2024年第一季度&#xff0c;华为的操作系统HarmonyOS在中国市场超越了苹果的iOS&#xff0c;成为中国市场上的第二大操作系统。 ![在这里插入…

Oracle 系列数据库使用 listagg去重,删除重复数据的几种方法

listagg聚合之后很多重复数据&#xff0c;下面是解决重复数据问题 案例表 create table "dept_tag_info" ( "tag_id" bigint not null, "tag_code" varchar(200), "tag_name" varchar(500), "tag_level" varchar(200), &…

解决ubuntu22.04共享文件夹问题

刚开机发现ubuntu里面的共享文件夹访问不了了 ubuntuwxy:/mnt/hgfs$ ls找了几篇博客&#xff0c;设置如下指令即可&#xff0c;记得退出当前目录重新进入刷新一下 sudo vmhgfs-fuse .host:/ /mnt/hgfs/ -o allow_other -o uid1000 仅供参考

数据库入门知识点

目录 常见简答问题 数据库系统概述 1.数据库基本概念 2.数据库系统的特点 3.DBMS 的主要功能? 4.数据库系统组成 5.数据库的三级模式定义 6.数据库两级映像的作用? 信息与数据模型 1.信息的三种世界是什么?彼此之间的联系是什么? 2.数据模型的三个要素: 3.数据库…

VL53L4CD TOF开发(4)----单次测量(One-Shot)模式

VL53L4CD TOF开发.4--单次测量&#xff08;One-Shot&#xff09;模式 概述视频教学样品申请完整代码下载实现demo硬件准备技术规格系统框图应用示意图生成STM32CUBEMX选择MCU串口配置IIC配置 XSHUTGPIO1X-CUBE-TOF1app_tof.c详细解释主程序演示结果 概述 最近在弄ST和瑞萨RA的…

展会预热|邀您共赴2024华南国际工业展览会

展会预告 在数字化转型的浪潮中&#xff0c;广东盘古信息科技股份有限公司&#xff08;以下简称“盘古信息”&#xff09;作为工业软件业内的领军企业&#xff0c;为制造企业提供全面的数字化生产制造运营管理系统及系统集成解决方案。我们将于2024年6月19日至21日亮相华南工博…

Nuxt3 实战 (九):使用 Supabase 实现 Github 认证鉴权

前言 Supabase 使用的是 postgresql 的 Row Level Security (RLS)&#xff0c;可以限制不同用户对同一张表的不同数据行的访问权限。这种安全机制可以确保只有授权用户才能访问其所需要的数据行&#xff0c;保护敏感数据免受未授权的访问和操作。 Auth Providers 打开 Supab…

图知识蒸馏综述:算法分类与应用分析

源自&#xff1a;软件学报 作者&#xff1a;陈哲涵 黎学臻 注&#xff1a;若出现无法显示完全的情况&#xff0c;可 V 搜索“人工智能技术与咨询”查看完整文章 摘 要 图数据, 如引文网络, 社交网络和交通网络, 广泛地存在现实生活中. 图神经网络凭借强大的表现力受到广泛…

5月70城房价:一线城市新建房价格唯上海反弹 二手房全线降幅扩大 M1下行的根源:地方政府压降债务

5月份&#xff0c;一线城市新建商品住宅销售价格同比下降3.2%&#xff0c;降幅比上月扩大0.7个百分点。其中&#xff0c;北京、广州和深圳分别下降1.8%、8.3%和7.4%&#xff0c;上海上涨4.5%。 2024年5月份&#xff0c;70个大中城市中&#xff0c;各线城市商品住宅销售价格环比…

Linux 下VS Code 弹出 快速修复,导致 BackSpace 无法删除

最近在Linux下使用VSCode&#xff0c;发现有错误的代码选中了无法删除 这个时候&#xff0c;你按BackSpace 是无法删除的&#xff0c;很恼火&#xff01; 把这个禁用了之后&#xff0c;就不会弹出这个框&#xff0c;这样可以顺利选中删除&#xff01; 感觉这个是不是vs code 插…

聚观早报 | GPT-4通过图灵测试;哪吒新车将交付

聚观早报每日整理最值得关注的行业重点事件&#xff0c;帮助大家及时了解最新行业动态&#xff0c;每日读报&#xff0c;就读聚观365资讯简报。 整理丨Cutie 6月17日消息 GPT-4通过图灵测试 哪吒新车将交付 SpaceX星舰将进行第五次试飞 马斯克称新款Roadster可以飞 华为…

跨平台兼容性优化:确保短剧APP小程序在不同系统上的稳定运行(二)

在上一篇帖子中&#xff0c;我们初步探讨了跨平台兼容性优化的重要性以及不同操作系统和设备对小程序的影响。本篇帖子将进一步深入分析如何优化代码和界面布局&#xff0c;以确保短剧APP小程序在不同平台上都能稳定运行。 一、代码优化策略 减少依赖和冗余&#xff1a;确保代…

Office办公软件如何下载安装?Office 2021最佳的办公软件安装包资源分享!

Office软件这种文档格式的普及&#xff0c;得益于其高度的兼容性和通用性&#xff0c;使得用户能够轻松地在不同的电脑和平台上打开和编辑文件。 Office软件文档格式的通用性&#xff0c;意味着无论是Windows、macOS还是Linux等操作系统&#xff0c;用户都能无障碍地打开和浏览…

triton矩阵乘以及缓存优化

这里triton.cdiv(M, META[BLOCK_SIZE_M]) * triton.cdiv(N, META[BLOCK_SIZE_N])&#xff0c;所以grid的形状是一维的。 观察函数内部 pid tl.program_id(axis0)&#xff0c;因为grid是一维的&#xff0c;所以这里就是总块数&#xff0c;我们假设实际A*BC&#xff0c; A&…

2024文件找回:怎么恢复删除的数据?6种实用的数据恢复汇总锦集!

怎么恢复删除的数据&#xff1f;在当今数字化的时代&#xff0c;我们的电脑中存储了大量的重要数据&#xff0c;一旦数据丢失&#xff0c;可能会对我们的工作和生活造成极大的困扰。因此&#xff0c;掌握一些有效的电脑数据恢复方法是非常必要的。下面&#xff0c;我将介绍六种…

【仿真建模-anylogic】Port原理解析

Author&#xff1a;赵志乾 Date&#xff1a;2024-06-14 Declaration&#xff1a;All Right Reserved&#xff01;&#xff01;&#xff01; 1. 类图 2. 原理解析 2.1 核心函数&#xff1a; Port作为各类型端口的基类&#xff0c;其核心方法如下 &#xff1a; 函数功能Port(A…

Github 2024-06-17 开源项目日报 Top10

根据Github Trendings的统计,今日(2024-06-17统计)共有10个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量JavaScript项目3Python项目3C++项目3Rust项目1C#项目1Tcl项目1Java项目1TypeScript项目1Godot引擎 – 多平台2D和3D游戏引擎 创建周期:3817 天…

Suno新技能亮相:完美复刻歌手音色,我甚至不敢公开!

之前写过一篇文章 颠覆音乐创作! Suno史诗级更新&#xff0c;随便哼哼就能出一首好听的歌曲&#xff1f; Suno支持上传一段音频或者自己的哼唱进行续创歌曲&#xff0c;这个功能大家有玩出花样嘛&#xff1f; 可能很多人&#xff0c;还不知道这个到底有啥用! 大家先看看这首《满…