抛弃Vuex,使用Pinia

这篇具有很好参考价值的文章主要介绍了抛弃Vuex,使用Pinia。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

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


抛弃Vuex,使用Pinia

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语法

抛弃Vuex,使用Pinia

定义模拟数据

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文章来源地址https://www.toymoban.com/news/detail-457098.html

<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>

到了这里,关于抛弃Vuex,使用Pinia的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Vue | Vue.js 全家桶 Pinia状态管理

    🖥️ Vue .js专栏:Node.js Vue.js 全家桶 Pinia状态管理 🧑‍💼 个人简介:一个不甘平庸的平凡人🍬 ✨ 个人主页:CoderHing的个人主页 🍀 格言: ☀️ 路漫漫其修远兮,吾将上下而求索☀️ 👉 你的一键三连是我更新的最大动力❤️ 目录 一、Pinia和Vuex的对比 什么是Pinia呢? Pina和

    2024年01月16日
    浏览(50)
  • Vue的鼠标键盘事件 pinia和vuex的区别 Vuex 和 Pinia 的优缺点

    鼠标事件(将v-on简写为@) 键盘事件 输入框事件 但是element-ui在实际使用时,前四条触发方法全部都是input方式( 在 Input 值改变时触发 )触发,遂使用原生的@blur才完成效果 表单输入相关修饰符  .lazy     input 输入完毕时 .number   input只获取数字类型的输入 .trim   去除用户输入中

    2024年02月12日
    浏览(43)
  • 【Vue】使用 Vuex 作为状态管理

    Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式和库。它使用单一状态树,这意味着这个对象包含了全部的应用层级状态,并且以一种相对集中的方式存在。这也意味着,通常单个项目中只有一个 Vuex store。Vuex 的核心概念和功能包括: 状态(State) :Vuex 使用单一状态树

    2024年01月23日
    浏览(58)
  • Vue使用 Pinia 进行状态管理

    Pinia 是一个适用于 Vue.js 的状态管理库,它采用了组合式 API 的理念,使得状态管理变得更加简单、直观和灵活。与传统的 Vuex 相比,Pinia 提供了更好的 TypeScript 支持,同时也更加适合大型应用程序和复杂状态逻辑的管理。 首先,我们需要在 Vue 项目中安装 Pinia。你可以通过

    2024年02月13日
    浏览(63)
  • vue 全局状态管理(简单的store模式、使用Pinia)

    多个组件可能会依赖同一个状态时,我们有必要抽取出组件内的共同状态集中统一管理,存放在一个全局单例中,这样任何位置上的组件都可以访问其中的状态或触发动作 通过自定义一个store模式实现全局的状态管理,实例如下 有两个组件a、b共享store和store2两个状态,我们

    2024年02月13日
    浏览(50)
  • Vue3 store仓库数据间流转 Vuex4 + Pinia

    上文 讲了vue2数据流转处理的方法 这文讲讲vue3 Vuex 在 Vue 3 中 可以使用 Vuex 4 来进行状态管理和存取数据。 1 创建一个 store 实例 2 在 main.js 中引入并挂载 store: 3 在组件中访问和修改数据: *** 在 Vue 3 中使用 Vuex 进行数据的存取和修改。state 存储数据,mutations 修改数据,ac

    2024年02月14日
    浏览(33)
  • Vue中的Pinia状态管理工具 | 一篇文章教会你全部使用细节

    Pinia(发音为/piːnjʌ/,如英语中的“peenya”)是最接近piña(西班牙语中的菠萝)的词 ; Pinia开始于大概2019年,最初是 作为一个实验为Vue重新设计状态管理 ,让它用起来适合组合式API(Composition API)。 从那时到现在,最初的设计原则依然是相同的,并且目前同时兼容Vue2、

    2024年02月11日
    浏览(39)
  • Vue3最佳实践 第六章 Pinia,Vuex与axios,VueUse 4(axios)

      axios 是一个基于 Promise 的 HTTP 客户端,不仅可以与 vue.js 一起使用,还可以与其他前端框架以及后端 Node.js 一起使用。当你想从包括后端服务器在内的外部服务获取数据时,可以使用axios 中的方法很方便的获得数据内容。在本文档中,我们将介绍如何在 vue.js 版本 3 的环境

    2024年02月07日
    浏览(36)
  • Vue状态管理库-Pinia

    Pinia 是 Vue 的专属状态管理库,它允许支持跨组件或页面共享状态,即共享数据,他的初始设计目的是设计一个支持组合式API的 Vue 状态管理库(因为vue3一个很大的改变就是组合式API),当然这并不是说Pinia只支持vue3,他是同时支持vue2和vue3的, 本文倾向于拥抱Vue3,所以代码语

    2024年02月22日
    浏览(52)
  • Vue——状态管理库Pinia

    写在前面 :本文参考小满大牛的pinia专栏 Vuex 和 Pinia 均是 Vue.js 的状态管理库,它们为 Vue 应用程序提供了一种集中式的、可预测的状态管理解决方案。 Vuex 是 Vue.js 官方推荐的状态管理库之一。它的核心概念包括 state、mutation、action 和 getter。其中,state 代表应用程序的状态

    2024年02月07日
    浏览(39)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包