Pinia 介绍、使用、实践

news2024/9/21 12:44:56

1. Pinia 介绍

1.1 Pinia 是什么

Pinia 官网

https://pinia.vuejs.org/

vuex Github

https://github.com/vuejs/vuex

上面是 Vuex Github 中置顶说明,我们可以得知:

  • Pinia 现在是新的默认设置,Vue 的官方状态管理库已更改为 Pinia,Vue3、4仍会维护,但不会添加新功能;

  • Pinia 具有与 Vuex 5 几乎完全相同或增强的 API,可以把他理解成 Vuex5,Pinia 适用于 Vue 2.x;

  • Pinia 和 Vuex 可以安装在同一个项目中,进行程序迁移;新项目强烈建议使用 Pinia;

1.2 Pinia 优势

Pinia 是 Vue.js 的轻量级状态管理库,最近很受欢迎。它使用 Vue 3 中的新反应系统来构建一个直观且完全类型化的状态管理库

  • 轻巧(体积约 1KB)

  • 完整的 TypeScript 支持

  • 代码更简洁,取消 mutations modules,不再有模块嵌套,不再有命名空间

  • Devtools 对 Pinia 的支持很好,store 出现在使用它们的组件中(这个我在后面会演示)

  • 服务器端渲染支持(这里我没有实际体验过,但是官网说:如果 export const state = reactive({}) 是服务器端呈现的,会使您的应用程序暴露于安全漏洞,而 Pinia 可以避免这种安全问题

2. Pinia 基本使用

2.1 Pinia 安装

创建一个 vite 项目(注意选择 vue-ts)

npm create vite@latest

安装 Pinia

yarn add pinia
npm install pinia

注意此处的 vue 版本,这之下的版本我并没有试过

2.2 Pinia 初步封装

2.2.1 在 main.ts 中,引入 Pinia

import { createPinia } from 'pinia'
app.use(createPinia())

2.2.2 定义 store

新建 store/index.ts

注意:将返回的函数命名为 use... 是跨可组合项的约定

import { defineStore } from 'pinia';

// 定义容器
export const useCountStore = defineStore('count', {
  // state 变量(推荐使用 完整类型推断的 箭头函数)
  state: () => ({
    // 所有属性都将自动推断其类型
    count: 0,
  }),
  // computed 计算属性
  getters: {},
  // actions 方法
  actions: {},
});

2.2.2.1 defineStore 方法接受的参数

  • id:字符串,表示 store 唯一 id,Pinia 使用它来将 store 连接到 Devtools

  • options:配置项,用于定义 store 需要的 变量、计算属性、方法

/**
* Creates a `useStore` function that retrieves the store instance
*
* @param id - id of the store (must be unique)
* @param options - options to define the store
*/
export declare function defineStore<Id extends string, S extends StateTree = {}, G extends _GettersTree<S> = {}, A = {}>(id: Id, options: Omit<DefineStoreOptions<Id, S, G, A>, 'id'>): StoreDefinition<Id, S, G, A>;

2.2.2.2 使用箭头函数定义 state 的两点原因

  • 服务器渲染,避免状态污染

  • 利于 TypeScript 类型推导

2.3 在组件中使用 store

2.3.1 直接使用 store


<template>
  <!-- 使用 store -->
  <button type="button" @click="addCount">count is: {{ store.count }}</button>
</template>

<script setup lang="ts">
  import { useCountStore } from '../store'
  
  const store = useCountStore()
  
  const addCount = () => {
    store.count++
  }
</script>

2.3.2 storeToRefs() 解构 store

store 是一个用 reactive 包裹的对象,这意味着不需要在 getter 之后写 .value,但是,就像 setup 中的 props 一样,不能对 store 进行直接解构,如果直接解构,会导致 store 中的变量,失去响应式效果

  • 错误示例:const { name, doubleCount } = store

通过使用 storeToRefs(),它将为 store 中的任何响应式属性创建 refs进而让我们可以采用解构赋值的形式,取得 store 中的某个变量(可以类比于 reactive,reactive 中的变量是通过 toRefs 解构出来的)

  • 可以被解构出来的都是响应式的,比如 state、getters

  • 不可以被解构出来的都是非响应式的,比如 actions

<template>
  <!-- 使用 store -->
  <!-- <button type="button" @click="addCount">count is: {{ store.count }}</button> -->

  <!-- 使用 storeToRefs 解构赋值 store -->
  <button type="button" @click="addCount">count is: {{ count }}</button>
</template>

<script setup lang="ts">
  import { useCountStore } from '../store'
  import { storeToRefs } from 'pinia'
  
  // store
  const store = useCountStore()
  // storeToRefs 解构赋值
  const { count }  = storeToRefs(store)
  
  // 错误示例:直接解构赋值
  // const { count } = store

  const addCount = () => {
    store.count++
  }
</script>

2.4 改变 store 中数据的三种方法

2.4.1 直接修改 store

// store
const store = useCountStore()
// storeToRefs 解构赋值
const { count, msg }  = storeToRefs(store)

/**
 * 改变 store 中的值 - 直接修改
 */
const addCount = () => {
  // 注意:此处不能直接使用解构出来的 count
  store.count++
  store.msg = 'Changed Message Directly'
  store.arr.push(8)
}

2.4.2 使用 $patch 修改 store

2.4.2.1 $patch 修改简单数据

/**
 * 改变 store 中的值 - $patch 修改简单数据
 */
const addCount = () => {
  // 注意:这里也不是直接赋值,而是要通过 store.count 这么一步
  store.$patch({
    // 下面这么写,会导致数据无法被改变
    // count: store.count++,

    // 下面这么写,会报错
    // arr: store.arr.push(4)

    count: store.count + 1,
    msg: 'Changed Message',
    arr: [...store.arr, 4],
  })
}

2.4.2.2 $patch 修改复杂数据

$patch 可以传入以下两种内容:

  • 一个对象:直接“重写”对象,逗号结尾(修改数组之类的,就比较费劲)

  • 接收 state 的箭头函数:通过语句修改 state 某个属性值,分号结尾(修改复杂数据,就没那么费劲了)

/**
* 改变 store 中的值 - $patch 修改复杂数据
*/
const addCount = () => {
  // 注意:这里接受了 state 参数,在回调函数里,通过一条条语句 修改 state
  store.$patch((state) => {
    // 下面这么写,会导致数据无法被改变
    // state.count = store.count++
    
    // 下面这么写,会报错
    // state.arr = state.arr.push(5)
    
    state.count = store.count + 1
    state.msg = 'Changed Message'
    
    state.arr.push(5)
  })
}

2.4.3 使用 actions 修改 store

在 index.ts 中定义 actions

import { defineStore } from 'pinia';

// 定义容器
export const useCountStore = defineStore('count', {
  // state 属性(必须是一个箭头函数)
  state: () => ({
    count: 0,
    msg: 'Hello Pinia',
    arr: [1, 2, 3],
  }),
  // computed 计算
  getters: {},
  // actions 方法
  actions: {
    // 定义一个 function
    changeState(num: number) {
      this.count += 10
      this.msg = 'Changed Message By Actions'
      this.arr.push(num)
    },
  },
});

在组件中通过 store.xxx 使用 actions(xxx 为 actions 中定义的方法名)

/**
 * 改变 store 中的值 - 使用 store.actions 中定义的方法
 */
const addCount = () => {
  store.changeState(66);
}

注意:actions 无法被解构出来,下面会报错

const { count, msg, arr, changeState } = storeToRefs(store)

2.4.4 复原 store、替换 state

恢复到 store 被修改之前的状态

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

只会替换原来就有的 count,不会自动添加原来没有的 name

const store = useStore()
store.$state = { count: 666, name: 'yeah' }

2.5 “计算属性”getters

2.5.1 getters 的基本使用

使用 getters 的方法,分为以下两步:

  • 在 index.ts 中定义 getters

  • 在组件中通过 store.xxx 使用 getters(xxx 为 getters 中定义的方法名)

计算属性是可以被解构出来的

在 index.ts 中定义 getters

import { defineStore } from 'pinia';

// 定义容器
export const useCountStore = defineStore('count', {
  // state 属性(必须是一个箭头函数)
  state: () => ({
    count: 0,
  }),
  // computed 计算
  getters: {
    // 通过 this 使用 state
    doubleCountThis(): number {
      return this.count * 2
    },
  },
});

在组件中使用 getters

  <!-- 通过 this 使用 state -->
  <h4>{{ store.doubleCountThis }}</h4>

2.5.2 getters 中的 state

getters 中定义的函数,可以接收 state,如图所示:

可以看出:参数state 包含了 上面的 state对象 定义的变量,不包含 getters 定义的“计算属性”

相较于 this 访问 state 中的数据,官网更推荐使用 state 接收参数,定义 getters

import { defineStore } from 'pinia';

// 定义容器
export const useCountStore = defineStore('count', {
  // state 属性(必须是一个箭头函数)
  state: () => ({
    count: 0,
  }),
  // computed 计算
  getters: {
    // 通过 this 使用 state
    doubleCountThis(): number {
      return this.count * 2
    },
    // 通过接受 state,避免使用 this
    doubleCountState(state): number {
      console.log('getters state ===', state);
      
      // 错误写法
      // return state.doubleCountThis
      
      return state.count * 4
    },
  },
});
  <!-- 通过接受 state,避免使用 this -->
  <h4>{{ store.doubleCountState }}</h4>

打印一下 getters 中的 state,如下图所示

看起来似乎包含了 getters 中定义的计算属性

但实际上,不可以通过 state.xxx 使用计算属性,会报错

2.5.3 在 getters 中访问其他 getters

前面说过,无法通过 getters 接收的 state 参数使用前面定义的 getters;

在 getters 中,只能通过 this 的方式,使用前面定义的 getters;

import { defineStore } from 'pinia';

// 定义容器
export const useCountStore = defineStore('count', {
  // state 属性(必须是一个箭头函数)
  state: () => ({
    count: 0,
  }),
  // computed 计算
  getters: {
    // 通过 this 使用 state
    doubleCountThis(): number {
      return this.count * 2
    },
    // 通过接受 state,避免使用 this
    doubleCountState(state): number {
      return state.count * 4
    },
    // getters 传递,只能通过 this
    transmitGetters(): number {
      return this.doubleCountState + 1
    }
  },
});
  <!-- getters 传递,只能通过 this -->
  <h4>{{ store.transmitGetters }}</h4>

2.5.4 getters 中接收参数

getters 只是幕后的 computed 属性,因此无法向它们传递任何参数

但是,可以从 getter 返回一个函数,以接受任何参数,举个栗子:

export const useCountStore = defineStore('count', {
  getters: {
    getUserById: (state) => {
      // 可以从 getter 返回一个函数,以接受任何参数
      return (userId) => state.users.find((user) => user.id === userId)
    },
    getArrItemByIndex(state) {
      return (index: number) => state.arr.find((item, i) => i === index);
    },
    totalCount(state) {
      return state.cartList.reduce((total, item) => {
        return (total += item.quantity);
      }, 0);
    },
  },
})  
<!-- getters 传参 -->
<h4>{{ store.getArrItemByIndex(1) }}</h4>

2.6 使用函数定义 store

可以使用一个函数(类似于一个组件 setup())来为更高级的用例定义一个 store

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

  return { count, increment }
})

由于我自己更希望使用 Pinia 的感觉更像 Vuex,所以我没有仔细看这个函数定义 store 的方法

他们的区别在于第二个参数接受的是一个配置对象,还是一个箭头函数

2.7 访问其他 store 中的内容

直接在当前 store 内部实例化其他 store,并使用里面的内容即可

state、getters、actions 都行

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

export const useCountStore = defineStore('count', {
  state: () => ({
    // ...
  }),
  
  getters: {
    otherGetter(state) {
      // 直接在当前 store 内部实例化其他 store,并使用里面的内容
      const otherStore = useOtherStore()
      return state.localData + otherStore.data
    },
  },
  
  actions: {
    async fetchUserPreferences(preferences) {
      // 直接在当前 store 内部实例化其他 store,并使用里面的内容
      const auth = useAuthStore()
      if (auth.isAuthenticated) {
        this.preferences = await fetchPreferences()
      } else {
        throw new Error('User must be authenticated')
      }
    },
  },
})

2.8 Pinia Plugin

2.8.1 什么是 Pinia 插件

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

export function myPiniaPlugin(context) {
  context.pinia // 使用 `createPinia()` 创建的 pinia
  context.app // 使用 `createApp()` 创建的当前应用程序(仅限 Vue 3)
  context.store // 插件正在扩充的 store
  context.options // 定义存储的选项对象传递给`defineStore()`
  // ...
}

使用 pinia.use() 将插件传递给 Pinia

pinia.use(myPiniaPlugin)

2.8.2 使用插件扩充 store

下面两种写法,都可以实现:给所有的 store 实例,添加一个静态属性 hello

推荐写法一,因为写法一可以让 devtools 自动跟踪 hello 属性,让此静态属性在 devtools 中可见

举个例子:给所有 store 实例,添加静态属性 hello

// 写法一(推荐)
pinia.use(() => ({ hello: 'world' }))

// 写法二
pinia.use(({ store }) => {
  store.hello = 'world'
})

2.8.3 使用插件添加外部属性、其他库实例、非响应式内容

当添加以下内容时,需要先使用 markRaw() 包装对象,然后再传递给 Pinia

  • 外部属性

  • 来自其他库的类实例(比如路由)

  • 仅仅是非响应式的内容

举个栗子:给每个 store 添加路由

import { markRaw } from 'vue'
// 路由
import { router } from './router'

pinia.use(({ store }) => {
  store.router = markRaw(router)
})

2.8.4 其他 Pinia 插件可以做的事

由于篇幅有限,上面我只列举了目前我在开发过程中,可能用到的情况

其实 Pinia 插件还可以做很多更高级的事情,有需要的同学建议以自行查阅

下面是官网列出的内容:

  • 向 Store 添加新属性

  • 定义 Store 时添加新选项

  • 为 Store 添加新方法

  • 包装现有方法

  • 更改甚至取消操作

  • 实现本地存储等副作用

  • 仅适用于特定 Store

Pinia Plugins 官方文档:https://pinia.web3doc.top/core-concepts/plugins.html

2. Pinia 综合演示

2.1 需求说明

商品列表

  • 展示商品列表

  • 添加到购物车

  • 计算购物车已有商品数量

购物车

  • 展示购物车商品列表

  • 计算总价格

  • 结算

2.2 Pinia 和 Devtools

devtools 官网文档:https://devtools.vuejs.org/plugin/plugins-guide.html

Pinia 和 Devtools 进行了很好的集成(如果你的工具没有出现小菠萝,则可以考虑重新装下 Devtools);

Devtools 会根据容器ID,列出所有的 store 实例(可以 容器ID 理解为:命名空间);

举个栗子:我定义了两个 store 实例: product、cart

Devtools 显示效果如图,它不仅列出了我定义的两个 store 实例,还允许我们对容器内容进行各种操作

同时,Devtools 会把当前组件用到的 Pinia 内容,映射到组件调试工具中,同样可以直接进行编辑

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

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

相关文章

数据结构与算法系列之时间与空间复杂度

这里写目录标题算法的复杂度大O的渐进表示法实例分析空间复杂度每日一题算法的复杂度 衡量一个算法的好坏&#xff0c;一般 是从时间和空间两个维度来衡量的&#xff0c; 即时间复杂度和空间复杂度。 时间复杂度主要衡量一个算法的运行快慢&#xff0c; 空间复杂度主要衡量一个…

Linux -- 程序 进程 线程 概念引入

程序与进程 &#xff1a;程序 &#xff1a;什么是程序 &#xff1f;&#xff1f;&#xff1f;伪官方 &#xff1a; 二进制文件&#xff0c;文件存储在磁盘中&#xff0c;例如 /usr/bin 目录下 。 是静态。 简单讲 &#xff1a;# 我们都学习了语言&#xff0c;比如下面这串代…

全国领先——液力悬浮仿生型人工心脏上市后在同济医院成功植入

2023年2月22日&#xff0c;华中科技大学同济医学院附属同济医院&#xff08;同济医院&#xff09;心脏大血管外科团队举办了一场气氛热烈的小规模庆祝活动&#xff0c;魏翔主任、程才副主任、王星宇副主任医师和李师亮医师到场&#xff0c;为终末期心衰患者黄先生“庆生”&…

Java 文本检索神器 “正则表达式”

Java 文本检索神器 “正则表达式” 每博一文案 在我们短促而又漫长的一生中&#xff0c;我们在苦苦地寻找人生的幸福&#xff0c;可幸福往往又与我们失之交臂&#xff0c; 当我们为此而耗尽宝贵的。青春年华&#xff0c;皱纹也悄悄地爬上了眼角的时候&#xff0c;我们或许才能…

Maven工程打jar包的N种方式

Maven工程打jar包 一、IDEA自带打包插件二、maven插件打包2.1 制作瘦包&#xff08;直接打包&#xff0c;不打包依赖包&#xff09;2.2 制作瘦包和依赖包&#xff08;相互分离&#xff09;2.3 制作胖包&#xff08;项目依赖包和项目打为一个包&#xff09;2.4 制作胖包&#xf…

数据结构与算法(二十)快速排序、堆排序(四)

数据结构与算法&#xff08;三&#xff09;软件设计(十九)https://blog.csdn.net/ke1ying/article/details/129252205 排序 分为 稳定排序 和 不稳定排序 内排序 和 外排序 内排序指在内存里&#xff0c;外排序指在外部存储空间排序 1、排序的方法分类。 插入排序&#xff…

下拉框推荐-Suggest-SUG

什么是下拉框推荐 在我们使用各种app&#xff08;飞猪&#xff09;想要搜索我们想要的东西&#xff0c;假设我想要上海迪士尼的门票&#xff0c;那么精确的query是“上海迪士尼门票”&#xff0c;要打7个字&#xff0c;如果在你输入“上海”的时候app就推荐了query“上海迪士尼…

无线蓝牙耳机哪个牌子好?2023质量好的无线蓝牙耳机推荐

近几年&#xff0c;随着蓝牙技术的不断进步&#xff0c;使用蓝牙耳机的人也越来越多。蓝牙耳机的出现&#xff0c;不仅能让我们摆脱线带来的约束&#xff0c;还能提升我们学习和工作的效率。最近看到很多人问&#xff0c;无线蓝牙耳机哪个牌子好&#xff1f;下面&#xff0c;我…

accent-color一行代码,让你的表单组件变好看

不做切图仔,从关注本专栏开始 文章目录 不做切图仔,从关注本专栏开始前言兼容性语法继承性智能前言 在之前的网站开发中,我们是很难去更改的你某些控件的颜色。我们可能要使用各种技巧来自定义我们的控件。好消息是,今天如果我们想要去改变控件的颜色,css为我们提供了一些…

docker删除已停止的容器

一、docker删除已停止的容器 1、根据容器的状态&#xff0c;删除Exited状态的容器 先停止容器、再删除镜像中的容器、最后删除none的镜像。执行命令如下&#xff1a; docker stop $(docker ps -a | grep "Exited" | awk {print $1 }) #停止容器 docker rm $(docke…

【C++初阶】1. C++入门

1. 前言 1. 什么是C C语言是结构化和模块化的语言&#xff0c;适合处理较小规模的程序。对于复杂的问题&#xff0c;规模较大的程序&#xff0c;需要高度的抽象和建模时&#xff0c;C语言则不合适。为了解决软件危机&#xff0c; 20世纪80年代&#xff0c; 计算机界提出了OOP(…

Python每日一练(20230228)

目录 1. 螺旋矩阵 II ★★ 2. 排列序列 ★★★ 3. 数字 1 的个数 ★★★ 1. 螺旋矩阵 II 给你一个正整数 n &#xff0c;生成一个包含 1 到 n2 所有元素&#xff0c;且元素按顺时针顺序螺旋排列的 n x n 正方形矩阵 matrix 。 示例 1&#xff1a; 输入&#xff1a;n 3…

java本地搭建宝塔部署实战likeadmin系统vue前端源码 - admin端(二)

大家好啊&#xff0c;我是测评君&#xff0c;欢迎来到web测评。 上一期给大家分享了java版likeadmin的server端本地搭建运行&#xff0c;宝塔部署的方式&#xff0c;今天来给大家分享admin前端vue在本地搭建&#xff0c;与打包发布到宝塔的方法。感兴趣的朋友可以自行下载学习。…

Unity——制作简易红绿灯

效果图与该类红绿灯相似。前提准备首先在场景中&#xff0c;创建一个正方体&#xff08;灯座&#xff09;&#xff0c;球体&#xff08;作为灯&#xff09;&#xff0c;把其放置成红绿灯结构。创建四个材质球&#xff0c;基础色分别赋为灰色&#xff0c;红色&#xff0c;黄色&a…

如何彻底删除SQL Server 2008中的登录账号

我个人遇到的最烦人的事情之一是 SQL Server Management Studio中“服务器名称和登录名”对话框的下拉列表。 以下是我想从 SSMS 连接屏幕中删除某些内容的两种情况: 键入的服务器名称不正确 服务器将来不需要。当我看到服务器的名称,它已经存在了很长一段时间,我知道我不会…

gismo中用等几何解决线弹性问题的程序示例---未完待续2023.2.28

文章目录前言一、调用线弹性程序示例1.1 对plateWithHole.xml文件的理解1.2 程序及注释1.3 对边界力函数的理解总结 #pic_center前言 gismo中用等几何解决线弹性问题 一、调用线弹性程序示例 1.1 对plateWithHole.xml文件的理解 算例来自文章&#xff1a;Isogeometric analysi…

操作系统真相还原_第7章:中断

文章目录7.1 中断分类外部中断内部中断7.2 中断描述符表中断门描述符中断描述符表寄存器IDTR中断处理过程中断发生时的栈变化7.3 可编程中断控制器8259A级联8259A8259A的编程7.4 编写中断处理程序中断初始化过程中断执行过程简单的中断处理程序中断处理程序改进思考7.5 8253定时…

PXC高可用集群(MySQL)

1. PXC集群概述 1.1. PXC介绍 Percona XtraDB Cluster&#xff08;简称PXC&#xff09; 是基于Galera的MySQL高可用集群解决方案Galera Cluster是Codership公司开发的一套免费开源的高可用方案PXC集群主要由两部分组成&#xff1a;Percona Server with XtraDB&#xff08;数据…

XXL-JOB

XXL-JOB介绍 XXL-JOB是一个轻量级分布式任务调度平台&#xff0c;其核心设计目标是开发迅速、学习简单、轻量级、易扩展。现已开放源代码并接入多家公司线上产品线&#xff0c;开箱即用。 官网&#xff1a;https://www.xuxueli.com/xxl-job/ 文档&#xff1a;分布式任务调度…

GO中sync 包的 RWMutex 读写互斥锁

文章目录背景RWMutex 简介代码验证多个协程请求读锁 RLock() 和 RLock()读写交错 RLock() 和 Lock()写入的时候读取读取的时候写入请求多个写Lock() 和 Lock()背景 Mutex 互斥锁是严格锁定读和写&#xff0c;如果我们需要单独对读或者写添加锁需要使用 sync包的RWMutex 针对读…