抛弃Vuex,使用Pinia

news2024/11/25 18:26:42

Pinia 符合直觉的 Vue.js 状态管理库

文章目录

    • Pinia 符合直觉的 Vue.js 状态管理库
      • 1.简介
      • 2.为什么要使用Pinia
      • 3.安装
        • 3.1 挂载pinia
      • 4.创建一个store容器
        • 4.1 Option 参数
        • 4.2 Setup 参数
      • 5.三个重要概念
        • 5.1 State
        • 5.2 Getter
        • **5.3 Action**
      • 6.购物车实例
        • 6.1 商品列表组件

在这里插入图片描述

1.简介

官网

Pinia 起始于 2019 年 11 月左右的一次实验,其目的是设计一个拥有组合式 API 的 Vue 状态管理库。从那时起,我们就倾向于同时支持 Vue 2 和 Vue 3,并且不强制要求开发者使用组合式 API,我们的初心至今没有改变。除了安装SSR 两章之外,其余章节中提到的 API 均支持 Vue 2 和 Vue 3。虽然本文档主要是面向 Vue 3 的用户,但在必要时会标注出 Vue 2 的内容,因此 Vue 2 和 Vue 3 的用户都可以阅读本文档。

2.为什么要使用Pinia

Pinia 是 Vue 的专属状态管理库,它允许你跨组件或页面共享状态。如果你熟悉组合式 API 的话,你可能会认为可以通过一行简单的 export const state = reactive({}) 来共享一个全局状态。对于单页应用来说确实可以,但如果应用在服务器端渲染,这可能会使你的应用暴露出一些安全漏洞。 而如果使用 Pinia,即使在小型单页应用中,你也可以获得如下功能:

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

3.安装

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

3.1 挂载pinia

在main.ts中挂载,使用createPinia()创建pinia实例

import { createApp } from 'vue'
import './style.css'
import App from "./App.vue";
import {createPinia} from "pinia";

const pinia = createPinia()

let app = createApp(App);
app.use(pinia)
app.mount('#app')

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 对象。

4.1 Option 参数

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

export const useCounterStore = defineStore('counter', {
  state: () => ({ count: 0 }),
  getters: {
    double: (state) => state.count * 2,
  },
  actions: {
    increment() {
      this.count++
    },
  },
})

你可以认为 state 是 store 的数据 (data),getters 是 store 的计算属性 (computed),而 actions 则是方法 (methods)。

4.2 Setup 参数

也存在另一种定义 store 的可用语法。与 Vue 组合式 API 的 setup 函数 相似,我们可以传入一个函数,该函数定义了一些响应式属性和方法,并且返回一个带有我们想暴露出去的属性和方法的对象。

export const useCounterStore = defineStore('counter', () => {
  const count = ref(0)
  function increment() {
    count.value++
  }

  return { count, increment }
})

Setup Store 中:

  • ref() 就是 state 属性
  • computed() 就是 getters
  • function() 就是 actions

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

5.三个重要概念

5.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,
    }
  },
})

使用state

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

const mainStore = useMainStore()
mainStore.sum++

重置state

可以通过调用 store 的 $reset() 方法将 state 重置为初始值。

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

变更state

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

mainStore.$patch({
  sum:mainStore.sum+1,
  count:mainStore.count+1,
})

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

mainStore.$patch(state => {
  state.sum += 1;
  state.count += 1
})

两种变更 store 方法的主要区别是,$patch() 允许你将多个变更归入 devtools 的同一个条目中。

5.2 Getter

Getter 完全等同于 store 的 state 的计算值。可以通过 defineStore() 中的 getters 属性来定义它们。推荐使用箭头函数,并且它将接收 state 作为第一个参数:

export const useMainStore = defineStore("main", {
  state: () => {
    return {
      sum: 1,
      count: 2
    }
  },
  /**
     * 类似与组件的computed
     * 具有缓存功能,当里面的值没有变化时,多次调用也只会执行一次  
     */
  getters: {
    // 自动推断出返回类型是一个 number
    douberSum(state) {
      return 2 * state.sum;
    },
    // 返回类型**必须**明确设置
    douberSumT():number{
      // 整个 store 的 自动补全和类型标注 ✨
      return 2* this.sum;
    }
  },

})

然后你可以直接访问 store 实例上的 getter 了:

<template>
  <p>Double count is {{ store.douberSum }}</p>
</template>

<script setup>
import { useCounterStore } from './counterStore'
const store = useCounterStore()
</script>

Getter 只是幕后的计算属性,所以不可以向它们传递任何参数。不过,你可以从 getter 返回一个函数,该函数可以接受任意参数:

export const useStore = defineStore('main', {
  getters: {
    getUserById: (state) => {
      return (userId) => state.users.find((user) => user.id === userId)
    },
  },
})

并在组件中使用:

<script setup>
import { useUserListStore } from './store'
const userList = useUserListStore()
const { getUserById } = storeToRefs(userList)
// 请注意,你需要使用 `getUserById.value` 来访问
// <script setup> 中的函数
</script>

<template>
  <p>User 2: {{ getUserById(2) }}</p>
</template>

请注意,当这样做时,getter 将不再被缓存,它们只是一个被你调用的函数。不过,可以在 getter 本身中缓存一些结果,虽然这种做法并不常见,但有证明表明它的性能会更好

访问其他 store 的 getter

想要使用另一个 store 的 getter 的话,那就直接在 getter 内使用就好:

import { useOtherStore } from './other-store'

export const useStore = defineStore('main', {
  state: () => ({
    // ...
  }),
  getters: {
    otherGetter(state) {
      const otherStore = useOtherStore()
      return state.localData + otherStore.data
    },
  },
})

5.3 Action

Action 相当于组件中的 method。它们可以通过 defineStore() 中的 actions 属性来定义,并且它们也是定义业务逻辑的最好选择。

import {defineStore} from "pinia";

export const useMainStore = defineStore("main", {

  state: () => {
    return {
      sum: 1,
      count: 2
    }
  },
  actions: {
    changeState(number: number) {
      this.sum += number;
      this.count += number;
    }
  }
})

类似 getter,action 也可通过 this 访问整个 store 实例,并支持完整的类型标注(以及自动补全✨)不同的是,action 可以是异步的,你可以在它们里面 await 调用任何 API,以及其他 action!

actions: {
  async loadAllProduct() {
    this.all = await getProducts();
  },

    decrementProduct(product: IProduct) {
      const ret = this.all.find(item => item.id == product.id)
      if (ret) {
        ret.inventory--
      }
    }
}

访问其他 store 的 action

想要使用另一个 store 的话,那你直接在 action 中调用就好了:

actions: {
  addProductToCart(product:IProduct){
    console.log(product)

    //减库存
    const shopStore = useShopStore();
    shopStore.decrementProduct(product)
  },
}

6.购物车实例

本次使用的是TS语法

在这里插入图片描述

定义模拟数据

export interface IProduct {
  id: number,
  title: string,
  price: number,
  inventory: number //库存
}

const products: IProduct[] = [
  {id: 1, title: "ipad 4 mini", price: 3250.5, inventory: 2},
  {id: 2, title: "iphone 14 pro max", price: 9899, inventory: 1},
  {id: 3, title: "macbook pro", price: 16000, inventory: 3},
]


//定义请求方法
export const getProducts = async () => {
  await wait(100)
  return products
}

async function wait(delay: number) {
  return new Promise((resolve) => setTimeout(resolve, delay))
}


export const buyProducts = async (totalPrice: number) => {
  console.log('结账金额:'+totalPrice)
  await wait(100);
  return Math.random() > 0.5
}

6.1 商品列表组件

useShopStore

import {defineStore} from "pinia";
import {getProducts, IProduct} from "../request/shop.ts";


export const useShopStore = defineStore("shop", {
  state: () => {
    return {
      all: [] as IProduct[]
    }
  },
  getters: {},
  actions: {
    async loadAllProduct() {
      this.all = await getProducts();
    },

    decrementProduct(product: IProduct) {
      const ret = this.all.find(item => item.id == product.id)
      if (ret) {
        ret.inventory--
      }
    }
  }
})

ShopComp.vue

<template>
<h2>商品列表</h2>
<ul>
  <li v-for="(item,index) in all" :key="index">
    <h4>{{ item.title + ' - ¥' + item.price + '  剩余数量:' + item.inventory }}</h4>
    <button :disabled="item.inventory<=0" @click="addCar(item)">添加到购物车</button>
    <br>

  </li>
  </ul>

</template>

<script setup lang="ts">
  import {useShopStore} from "../../store/shop.ts";
  import {useCarStore} from "../../store/car.ts";
  import {IProduct} from "../../request/shop.ts";
  import {storeToRefs} from "pinia";

  const shopStore = useShopStore();

  //获取所有数据
  shopStore.loadAllProduct();

  const {all} = storeToRefs(shopStore);
  const carStore = useCarStore();

  const addCar = (product: IProduct) => {
    carStore.addProductToCart(product);
  }

</script>

<style scoped>
  * {
    margin: 0;
    padding: 0;
  }

</style>

6.2 购物车列表组件

useCartStore

import {defineStore} from "pinia";
import {buyProducts, IProduct} from "../request/shop.ts";
import {useShopStore} from "./shop.ts";

type CartProduct = {
  quantity:number//数量
}&IProduct

export const useCarStore = defineStore("car", {
  state() {
    return {
      cartProducts: [] as CartProduct[]//购物车列表
    }
  },
  getters: {
    totalPrice():number{
      return this.cartProducts.reduce((total,item)=>{
        console.log(total,item,item.price,item.quantity)
        return total+item.price*item.quantity
      },0)
    }
  },
  actions: {
    addProductToCart(product:IProduct){
      console.log(product)
      //判断是否还有库存
      if(product.inventory<1){
        alert("已经没有库存了")
        return
      }
      //有库存则将数据保存进去
      //检查购物车是否已经存在该商品
      const cartItem = this.cartProducts.find(item=>item.id===product.id);
      if (cartItem){
        cartItem.quantity+=1
      }else {
        this.cartProducts.push({...product,quantity:1})
      }

      //减库存
      const shopStore = useShopStore();
      shopStore.decrementProduct(product)

    },

    async settlementCart(){
      let data = await buyProducts(this.totalPrice)
      if (data){
        this.cartProducts=[];
      }
      return data

    }
  }
})

CartComp.vue

<template>
<h2>你的购物车</h2>
<h5>请添加一些商品到购物车</h5>
<ul>
  <li v-for="(item,index) in cartProducts" >
    <h5>{{item.title +"  -  "+item.price+"  * "+item.quantity}}</h5>
  </li>
  </ul>

<h5>商品总价:{{ '¥ '+totalPrice }}</h5>
<button @click="settlement">结算</button>
<h6 v-show="showFlag">{{msg}}</h6>

</template>

<script setup lang="ts">
  import {useCarStore} from "../../store/car.ts";
  import {storeToRefs} from "pinia";
  import {ref} from "vue";

  const carStore = useCarStore();
  const {cartProducts,totalPrice } = storeToRefs(carStore)

  let msg = ref("结算成功")
  let showFlag = ref(false)

  const settlement=()=>{
    let res = carStore.settlementCart();
    if(res){
      msg=ref("结算成功")
      showFlag.value=true;
      setTimeout(()=>{
        showFlag.value=false;
      },1000)
    }else {
      msg=ref("结算失败")
      showFlag.value=true;
      setTimeout(()=>{
        showFlag.value=false;
      },1000)
    }
  }

</script>

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

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

相关文章

【Python-Django】如何在一个项目中创建多个app模块

django开发案例&#xff1a;a​​​​​【Django】开发日报_1_Day&#xff1a;用户管理系统案例-创建项目_django 开发用户管理系统_代码骑士的博客-CSDN博客 前面做过的管理系统项目功能比较单一&#xff0c;只用一个模块就能解决问题。如果想创建多个不同的模块的话&#xf…

Nodejs之HTTP模块

目录 前言一&#xff0c;创建HTTP模块1.1 基本使用1.2 注意事项 二&#xff0c;查看报文2.1 浏览器查看HTTP报文2.2 利用request获取HTTP请求报文2.2.1 获取请求行和请求头 2.2.2 获取请求路径2.2.3 获取查询字符串 三&#xff0c;跟请求报文相关的练习四&#xff0c;设置响应报…

聊天机器人开发实战--(微信小程序+SpringCloud+Pytorch+Flask)【完整版】

文章目录 前言架构小程序端管理端运维端 交互聊天页面聊天发送流程历史聊天记录个人中心 总结 前言 没想到从五一之后&#xff0c;到现在鸽了那么久。没办法&#xff0c;实话实说&#xff0c;确实忙&#xff0c;五一期间就没怎么休息&#xff0c;开局第一周&#xff0c;准备IE…

Scala学习(七)---面向对象特质

文章目录 1.面向对象特质(Trait)2.特质声明2.1 特质的特点2.2 特质冲突2.3 特质叠加2.4 特质自身类型2.5 特质和抽象类的区别扩展 1.面向对象特质(Trait) 在Scala语言中&#xff0c;采用特质trait(特征)来代替接口的概念&#xff0c;也就是说&#xff0c;多个类具有相同的特质…

分享7款优质免费的Figma“中文插件”

今天我要向你们分享一些 Figma 的“中文插件”&#xff0c;这些插件不仅功能强大&#xff0c;而且免费使用&#xff0c;非常方便&#xff0c;而且是完全中文的页面使用起来非常便捷&#xff0c;可以大大提高设计师的效率和体验。 1、「平面 3D」 插件 首先是平面 3D 插件。该…

OpenVINO 2022.3之九:Post-training Optimization Tool (POT)

OpenVINO 2022.3之九&#xff1a;Post-training Optimization Tool (POT) Post-training Optimization Tool (POT) 通过在已训练好的模型上应用量化算法&#xff0c;将模型的权重和激活函数从 FP32/FP16 的值域映射到 INT8 的值域中&#xff0c;从而实现模型压缩&#xff0c;以…

驱动开发:内核实现进程汇编与反汇编

在笔者上一篇文章《驱动开发&#xff1a;内核MDL读写进程内存》简单介绍了如何通过MDL映射的方式实现进程读写操作&#xff0c;本章将通过如上案例实现远程进程反汇编功能&#xff0c;此类功能也是ARK工具中最常见的功能之一&#xff0c;通常此类功能的实现分为两部分&#xff…

WXSS 模板样式

WXSS WXSS&#xff08;WeiXin Style Sheets&#xff09;是一套样式语言&#xff0c;用来美化 WXML 的组件样式&#xff0c;类似于网页开发中的 CSS WXSS 和 CSS 的关系 WXSS 具有 CSS 大部分特性&#xff0c;同时&#xff0c;WXSS 还对 CSS 进行了扩充以及修改&#xff0c;以…

AJ-Report是一个完全开源,拖拽编辑的可视化设计工具

简介 AJ-Report是全开源的一个BI平台&#xff0c;酷炫大屏展示&#xff0c;能随时随地掌控业务动态&#xff0c;让每个决策都有数据支撑。     多数据源支持&#xff0c;内置mysql、elasticsearch、kudu驱动&#xff0c;支持自定义数据集省去数据接口开发&#xff0c;目前已支…

分布式应用之监控平台zabbix

1.监控系统的相关知识 1.1 监控系统运用的原因 当我们需要实时关注与其相关的各项指标是否正常&#xff0c;往往存在着很多的服务器、网络设备等硬件资源&#xff0c;如果我们想要能够更加方便的、集中的监控他们&#xff0c;zabix可以实现集中监控管理的应用程序 监控的初衷…

历届蓝桥杯青少年编程比赛 计算思维题真题解析【已更新3套 持续更新中】

一、计算思维组考试范围 计算思维组面向小学生&#xff08;7-12 岁&#xff0c;约 1-6 年级&#xff09;&#xff0c;通过设计多个角度的考核题目、层次科学的试卷组合、线上限时的考试形式&#xff0c;更加精确地考查学生的计算能力、反应能力、思维与分析能力&#xff0c;使…

【产品成长】产品专业化提升路径

产品专业化 产品专业化就是上山寻路。梳理一套作为产品经理的工作方法。 以图为例&#xff0c;做一个归纳。 第一&#xff1a;梳理自己的设计方法。就是拿到一个需求点之后&#xff0c;如何进行需求分析&#xff0c;如何还原业务情况&#xff0c;最终进行产品设计&#xff0c…

基于QEMU的RISC-V架构linux系统开发(三)——基于buildroot的最小根文件系统配置与编译

1.buildroot官网下载最新版本的buildroot。 https://buildroot.org/download.html 图1 下载最新版本的buildroot压缩包 2.拷贝buildroot软件包到工作目录&#xff0c;并解压buildroot。 图2 解压buildroot软件包 3.新建编译脚本build_risc-v.sh&#xff0c;使用buildroot自带的…

深度学习笔记之递归网络(五)递归神经网络的反向传播过程

机器学习笔记之递归网络——递归神经网络的反向传播过程 引言回顾&#xff1a;递归神经网络的前馈计算过程场景构建前馈计算描述 反向传播过程各参数的梯度计算各时刻损失函数梯度计算损失函数对各时刻神经元输出的梯度计算 Softmax \text{Softmax} Softmax回归的梯度计算关于 …

JAVA将xml数据转为实体类

使用 JAXB&#xff08;Java Architecture for XML Binding) 实现XML与Bean的相互转换 介绍 JAXB是一个业界的标准&#xff0c;是一项可以根据XML Schema产生Java类的技术。该过程中&#xff0c;JAXB也提供了将XML实例文档反向生成Java对象树的方法&#xff0c;并能将Java对象…

2023年7大人工智能技术趋势你有了解过嘛

人工智能 (AI) 已经接管世界&#xff0c;并且将在2023年继续向前发展。在2023年&#xff0c;它将完全实现自动化供应链、虚拟助手等多个产品与形态。 如今&#xff0c;世界正在经历一波人工智能驱动的全球经济转型浪潮。 当前之态势&#xff0c;人工智能 (AI) 技术几乎在每个领…

如何用Python进行屏幕录制?

文章目录 引言gpt3.5给出的代码更换截图函数——ImageGrab.grab禁用imshow解决递归现象摄像头录制代码后期需求 引言 关于屏幕录制这个功能需求&#xff0c;之前用过基于ffmpeg的Capture录屏软件&#xff0c;但是fps拉高以后会变得很卡&#xff0c;声音也同样出现卡顿。也自己…

cpu 内核 逻辑处理器的关系

6核CPU&#xff0c;12个逻辑处理器 一颗内核在一个时间片内只能执行一个内核线程&#xff1b;当物理CPU使用了超线程技术后&#xff0c;在CPU的一颗内核中&#xff0c;利用就是利用其中空闲的执行单元&#xff0c;模拟出另外一个核心&#xff08;并不是真正的物理运算核心&…

公司招人,面了一个5年经验不会自动化的测试人,他凭什么要18K?

在深圳这家金融公司也待了几年&#xff0c;被别人面试过也面试过别人&#xff0c;大大小小的事情也见识不少&#xff0c;今天又是团面的一天&#xff0c; 一百多个人都聚集在一起&#xff0c;因为公司最近在谈项目出来面试就2个人&#xff0c;无奈又被叫到面试房间。 整个过程…