vue3 Pinia快速入门

news2024/9/20 16:35:07

为什么是Pinia

怎么说呢,其实在过往的大部分项目里面,我并没有引入过状态管理相关的库来维护状态。因为大部分的业务项目相对来说比较独立,哪怕自身功能复杂的时候,可能也仅仅是通过技术栈自身的提供的状态管理能力来处理业务场景问题,比如React中的context,基本都能解决我遇到的问题。

针对Redux或者Vuex这类状态管理的库,我认为在较为复杂的大型业务场景下才能发挥他们的真实作用,在场景较为简单单一,数据状态相对不复杂的时候,引入他们可能并不能带来那么多的便捷。

说回Pinia,接触使用到它主要是因为有一次手里的发布功能需要进行重构。虽然仅仅是一个发布页面,但是梳理起来发现,里面涉及几类数据需要维护,包括主表单信息、错误校验信息、公共弹窗信息以及关联用户的各种状态信息等。想起之前有看到过关于小🍍的介绍,抱着试试看的心态接入试试。(不行我就继续Provide/Inject了[捂脸])

认识Pinia

Pinia 是 Vue 的存储库,它允许您跨组件/页面共享状态。

上面这个是官网对Pinia的一个定义,从定义上我们其实可以看出来,它可能比Vuex要精炼一些(Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式 + 库。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。)具体如何我们还是得具体从使用情况来看一下。

Pinia的常规用法

安装

通过常用的包管理器进行安装:

yarn add pinia
// 或者
npm install pinia
复制代码

安装完成后,我们需要将pinia挂载到Vue应用中,也就是我们需要创建一个根存储传递给应用程式。我们需要修改main.js,引入pinia提供的cteatePinia方法:

import { createApp } from 'vue';
import { createPinia } from 'pinia';
​
const pinia = createPinia();
const app = createApp(App);
app.use(pinia).mount('#app');
复制代码

上述安装引入基于Vue3,如果使用Vue2的话,轻参照官网相关说明即可。

Store

store简单来说就是数据仓库的意思,我们 的数据都放在store里面。当然你也可以把它理解为一个公共组件,只不过该公共组件只存放数据,这些数据我们其它所有的组件都能够访问且可以修改。它有三个概念,stategettersactions,我们可以将它们等价于组件中的“数据”、“计算属性”和“方法”。

store中应该包含可以在整个应用中访问的数据、全局性数据,我们应该避免将可以管理在具体组件内部的数据放到store中。

我们需要使用pinia提供的defineStore()方法来创建一个store,该store用来存放我们需要全局使用的数据。我们可以在项目中创建store目录存储我们定义的各种store:

// src/store/formInfo.js
import { defineStore } from 'pinia';
​
// 第一个参数是应用程序中 store 的唯一 id
const useFormInfoStore = defineStore('formInfo', {
  // 其他配置项,后面逐一说明
})
​
export default useFormInfoStore;
复制代码

defineStore接收两个参数:

  • name:一个字符串,必传项,该store的唯一id。
  • options:一个对象,store的配置项,比如配置store内的数据,修改数据的方法等等。

将返回的函数命名为 use... 是组合式开发的约定,使其符合使用习惯。我们可以根据项目情况定义任意数量的store存储不同功能模块的数据,一个store就是一个函数,它和Vue3的实现思想也是一致的。

使用store

我们可以在任意组件中引入定义的store来进行使用

<script setup>
// 引入定义
import useFormInfoStore from '@/store/formInfo';
// 调用方法,返回store实例
const formInfoStore = useFormInfoStore();
</script>
复制代码

store 被实例化后,你就可以直接在 store 上访问 stategettersactions 中定义的任何属性。

解构store

store 是一个用reactive 包裹的对象,这意味着不需要在getter 之后写.value,但是,就像setup 中的props 一样,我们不能对其进行解构,如果我们想要提取store中的属性同时保持其响应式的话,我们需要使用storeToRefs(),它将为响应式属性创建refs。

<script setup>
import { storeToRefs } from 'pinia';
// 引入定义
import useFormInfoStore from '@/store/formInfo';
// 调用方法,返回store实例
const formInfoStore = useFormInfoStore();
​
const { name, age } = formInfoStore; // ❌ 此时解构出来的name和age不具有响应式
​
const { name, age } = storeToRefs(formInfoStore); // ✅ 此时解构出来的name和age是响应式引用
</script>
复制代码

State

store是用来存储全局状态数据的仓库,那自然而然需要有地方能够保存这些数据,它们就保存在state里面。defineStore传入的第二个参数options配置项里面,就包括state属性。

// src/store/formInfo.js
import { defineStore } from 'pinia';
​
const useFormInfoStore = defineStore('formInfo', {
   // 推荐使用 完整类型推断的箭头函数
   state: () => {
      return {
        // 所有这些属性都将自动推断其类型
        name: 'Hello World',
        age: 18,
        isStudent: false
      }
   }
  
   // 还有一种定义state的方式,不太常见,了解即可
   // state: () => ({
   //    name: 'Hello World',
   //    age: 18,
   //    isStudent: false
   // })
})
​
export default useFormInfoStore;
复制代码

访问state

默认情况下,您可以通过 store 实例来直接读取和写入状态:

<script setup>
import useFormInfoStore from '@/store/formInfo';
const formInfoStore = useFormInfoStore();
​
console.log(formInfoStore.name); // 'Hello World'
formInfoStore.age++;  // 19
</script>
复制代码

pinia还提供了几个常见场景的方法供我们使用来操作state:$reset$patch$state$subscribe

<script setup>
import useFormInfoStore from '@/store/formInfo';
const formInfoStore = useFormInfoStore();
​
console.log(formInfoStore.name); // 'Hello World'
// 直接修改state中的属性
formInfoStore.age++;  // 19
​
// 1.$reset 重置状态,将状态重置成为初始值
formInfoStore.$reset();
console.log(formInfoStore.age); // 18
  
// 2.$patch 支持对state对象的部分批量修改
formInfoStore.$patch({
    name: 'hello Vue',
    age: 198
});
  
// 3.$state 通过将其 $state 属性设置为新对象来替换 Store 的整个状态
formInfoStore.$state = {
  name: 'hello Vue3',
  age: 100,
  gender: '男'
}
​
// 4.$subscribe 订阅store中的状态变化
formInfoStore.$subscribe((mutation, state) => {
  // 监听回调处理
}, {
  detached: true  // 💡如果在组件的setup中进行订阅,当组件被卸载时,订阅会被删除,通过detached:true可以让订阅保留
})
</script>
复制代码

针对上面示例,有几点需要关注一下:

  • 1.同直接修改state中的属性不同,通过$patch方法更新多个属性时,在devtools的timeline中,是合并到一个条目中的。

  • 2.通过实验得知,$state在进行替换时,会更新已有的属性,新增原来state不存在的属性,针对不在替换范围内的,则保持不变。

如上图,针对gender属性,执行的是"add"操作,然后这个替换过程我们没有设置isStudent属性,它仍然保持原状态在state中不变。

  • 3.subscribe只会订阅到patches引起的变化,即上面的通过subscribe只会订阅到patches引起的变化,即上面的通过subscribe只会订阅到patches引起的变化,即上面的通过patch方法和$state覆盖时会触发到状态订阅,直接修改state的属性则不会触发。

Getters

pinia中的getters可以完全等同于Store状态的计算属性,通常在defineStore中的getters属性中定义。同时支持组合多个getter,此时通过this访问其他getter。

import { defineStore } from 'pinia';
​
const useFormInfoStore = defineStore('formInfo', {
    state: () => {
        return {
            name: 'Hello World',
            age: 18,
            isStudent: false,
            gender: '男'
        };
    },
    getters: {
        // 仅依赖state,通过箭头函数方式
        isMan: (state) => {
            return state.gender === '男';
        },
        isWoman() {
            // 通过this访问其他getter,此时不可以用箭头函数
            return !this.isMan;
        }
    }
});
​
export default useFormInfoStore;
​
复制代码

在使用时,我们可以直接在store实例上面访问getter:

<template>
  <div>The person is Man: {{ formInfoStore.isMan }} or is Woman: {{ formInfoStore.isWoman }}</div>
</tempalte>
​
<script setup>
import useFormInfoStore from '@/store/formInfo';
const formInfoStore = useFormInfoStore();
</script>
复制代码

通常getter是不支持额外传参的,但是我们可以从getter返回一个函数的方式来接收参数:

import { defineStore } from 'pinia';
​
const useFormInfoStore = defineStore('formInfo', {
    state: () => {
        return {
            name: 'Hello World',
            age: 18,
            isStudent: false,
            gender: '男'
        };
    },
    getters: {
        isLargeBySpecialAge: (state) => {
          return (age) => {
             return state.age > age
          }
        }
    }
});
​
export default useFormInfoStore;
复制代码

在组件中使用时即可传入对应参数,注意,在这种方式时,getter不再具有缓存性

<template>
  <div>The person is larger than 18 years old? {{ formInfoStore.isLargeBySpecialAge(18) }}</div>
</tempalte>
​
<script setup>
import useFormInfoStore from '@/store/formInfo';
const formInfoStore = useFormInfoStore();
</script>
复制代码

Actions

actions相当于组件中的methods,它们定义在defineStore中的actions属性内,常用于定义业务逻辑使用。action可以是异步的,可以在其中await 任何 API 调用甚至其他操作

import { defineStore } from 'pinia';
​
const useFormInfoStore = defineStore('formInfo', {
    state: () => {
        return {
            name: 'Hello World',
            age: 18,
            isStudent: false,
            gender: '男'
        };
    },
    getters: {
        isMan: (state) => {
            return state.gender === '男';
        },
        isWoman() {
            return !this.isMan;
        }
    },
    actions: {
        incrementAge() {
            this.age++;
        },
        async registerUser() {
            try {
                const res = await api.post({
                    name: this.name,
                    age: this.age,
                    isStudent: this.isStudent,
                    gender: this.gender
                });
                showTips('用户注册成功~');
            } catch (e) {
                showError('用户注册失败!');
            }
        }
    }
});
​
export default useFormInfoStore;
​
复制代码

使用起来也非常方便

<script setup>
import useFormInfoStore from '@/store/formInfo';
const formInfoStore = useFormInfoStore();
  
const registerUser = () => {
  formInfoStore.registerUser();
}
</script>
复制代码

$onAction()

可以使用 store.$onAction() 订阅 action 及其结果。传递给它的回调在 action 之前执行。 after 处理 Promise 并允许您在 action 完成后执行函数,onError 允许您在处理中抛出错误。

const unsubscribe = formInfoStore.$onAction(
  ({
    name, // action 的名字
    store, // store 实例
    args, // 调用这个 action 的参数
    after, // 在这个 action 执行完毕之后,执行这个函数
    onError, // 在这个 action 抛出异常的时候,执行这个函数
  }) => {
    // 记录开始的时间变量
    const startTime = Date.now()
    // 这将在 `store` 上的操作执行之前触发
    console.log(`Start "${name}" with params [${args.join(', ')}].`)
​
    // 如果 action 成功并且完全运行后,after 将触发。
    // 它将等待任何返回的 promise
    after((result) => {
      console.log(
        `Finished "${name}" after ${
          Date.now() - startTime
        }ms.\nResult: ${result}.`
      )
    })
​
    // 如果 action 抛出或返回 Promise.reject ,onError 将触发
    onError((error) => {
      console.warn(
        `Failed "${name}" after ${Date.now() - startTime}ms.\nError: ${error}.`
      )
    })
  }
)
​
// 手动移除订阅
unsubscribe()
复制代码

和$subscribe类似,在组件中使用时,组件卸载,订阅也会被删除,如果希望保留的话,需要传入true作为第二个参数。

<script setup>
import useFormInfoStore from '@/store/formInfo';
const formInfoStore = useFormInfoStore();
​
formInfoStore.$onAction(callback, true);
</script>
复制代码

Pinia的基础使用我们暂时介绍到这里,其他使用场景大家可以参照官方文档进一步学习。

Pinia VS Vuex

回过头来,我们再来看一下,Pinia为什么现在这么受到推崇。和我们过往常用的Vuex相比,它到底好在哪里呢?

对于Vuex,我们知道,它的背后基本思想借鉴了Flux。Flux 是 Facebook 在构建大型 Web 应用程序时为了解决数据一致性问题而设计出的一种架构,它是一种描述状态管理的设计模式。绝大多数前端领域的状态管理工具都遵循这种架构,或者以它为参考原型。Vuex在它的基础上进行了一些设计上的优化:

Vuex主要有五部分核心内容:

  • 📦 state:整个应用的状态管理单例,等效于 Vue 组件中的 data,对应了 Flux 架构中的 store。

  • 🧮 getter:可以由 state 中的数据派生而成,等效于 Vue 组件中的计算属性。它会自动收集依赖,以实现计算属性的缓存。

  • 🛠 mutation:类似于事件,包含一个类型名和对应的回调函数,在回调函数中可以对 state 中的数据进行同步修改。

    • Vuex 不允许直接调用该函数,而是需要通过 store.commit 方法提交一个操作,并将参数传入回调函数。
    • commit 的参数也可以是一个数据对象,正如 Flux 架构中的 action 对象一样,它包含了类型名 type 和负载 payload
    • 这里要求 mutation 中回调函数的操作一定是同步的,这是因为同步的、可序列化的操作步骤能保证生成唯一的日志记录,才能使得 devtools 能够实现对状态的追踪,实现 time-travel。
  • 🔨 action:action 内部的操作不受限制,可以进行任意的异步操作。我们需要通过 dispatch 方法来触发 action 操作,同样的,参数包含了类型名 type 和负载 payload

    • action 的操作本质上已经脱离了 Vuex 本身,假如将它剥离出来,仅仅在用户(开发者)代码中调用 commit 来提交 mutation 也能达到一样的效果。
  • 📁 module:store 的分割,每个 module 都具有独立的 state、getter、mutation 和 action。

    • 可以使用 module.registerModule 动态注册模块。
    • 支持模块相互嵌套,可以通过设置命名空间来进行数据和操作隔离。

通过和我们上面学习到的Pinia的基础内容对比可以看出,Pinia舍弃了mutation和module两部分,这样我们在使用时就更加的便捷。

与Vuex3.x/4.x对比,主要区别如下:

  • mutations 不再存在。他们经常被认为是 非常 冗长。他们最初带来了 devtools 集成,但这不再是问题。
  • 无需创建自定义复杂包装器来支持 TypeScript,所有内容都是类型化的,并且 API 的设计方式尽可能利用 TS 类型推断。
  • 不再需要注入、导入函数、调用函数、享受自动完成功能!
  • 无需动态添加 Store,默认情况下它们都是动态的,您甚至都不会注意到。请注意,您仍然可以随时手动使用 Store 进行注册,但因为它是自动的,您无需担心。
  • 不再有 modules 的嵌套结构。您仍然可以通过在另一个 Store 中导入和 使用 来隐式嵌套 Store,但 Pinia 通过设计提供平面结构,同时仍然支持 Store 之间的交叉组合方式。 您甚至可以拥有 Store 的循环依赖关系
  • 没有 命名空间模块。鉴于 Store 的扁平架构,“命名空间” Store 是其定义方式所固有的,您可以说所有 Store 都是命名空间的。

其实对于我来说,之所以选择Pinia,甚至是喜欢上它,是因为它和Vue3的组合是API形式更加贴合,只需要把它当作一个特殊的数据状态组件来使用就好,不需要那么复杂的流程。

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

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

相关文章

huggingface下载的.arrow数据集读取与使用说明

1.数据下载方式&#xff1a;load_dataset 将数据集下载到本地&#xff1a;&#xff08;此处下载的是一个物体目标检测的数据集&#xff09; from datasets import load_dataset # 下载的数据集名称, model_name keremberke/plane-detection # 数据集保存的路径 save_path da…

苹果Mac电脑清理垃圾软件卸载工具CleanMyMac X

最近刚刚入手了一台 M1 Macbook&#xff0c;因为不是很懂下载了很多软件&#xff0c;然后又卸载了一些&#xff0c;导致系统内存在很多垃圾文件&#xff0c;我也不知道怎么清理&#xff0c;后来查询了一些资料&#xff0c;大家都普遍推荐 CleanMyMac X&#xff0c;于是经过我一…

触摸屏是如何诞生的,它又是如何影响和改变着我们的生活?

芊芊玉指在小小的屏幕上滑动&#xff0c;天下事便了然于胸。这就是手机触摸屏给我们的生活带来的改变。 曾几何时&#xff0c;我们是生活在九宫格或者全键盘上的“拇指族”。一股浪潮席卷而来&#xff0c;手机上的实体按键都消失了&#xff0c;虚拟按键仅在需要时出现。触摸屏是…

论文实验1、安装tensorflow运行节点嵌入相关方法

还是官方的教程好使 使用 pip 安装 TensorFlow 只有三步 1.安装python&#xff0c;版本太高不行&#xff0c;在推荐版本里选最高的。 2.安装python虚拟环境venv python -m venv --system-site-packages .\venv .\venv\Scripts\activate 3.在虚拟环境里装tensorflow pip…

vue的watch侦听器、watch的属性 immediate(侦听属性)、deep(侦听一个对象)

1.什么是watch侦听器 watch侦听器允许开发者监视数据的变化&#xff0c;从而针对数据的变化做特定的操作。 语法格式如下: const vm new Vue({el: #app,data: { username: },watch: {//监听username值的变化// newVal 是"变化后的新值”&#xff0c;oldVal 是"变…

Golang每日一练(leetDay0046)

目录 136. 只出现一次的数字 Single Number &#x1f31f; 137. 只出现一次的数字 II Single Number II &#x1f31f;&#x1f31f; 260. 只出现一次的数字 III Single Number III &#x1f31f;&#x1f31f;&#x1f31f; &#x1f31f; 每日一练刷题专栏 &#x1f3…

linux docker搭建Zfile

1.下载镜像 docker pull stilleshan/zfile2.创建挂载目录 mkdir -p /opt/docker/zfile #自定义路径3.运行 docker run -d --namezfile --restartalways -p 1111:8080 \-v /opt/docker/zfile/conf:/root/.zfile-v4 \-v /opt/docker/zfile/data:/root/zfile/data \stillesha…

C/C++每日一练(20230425)

目录 1. 成绩分布 ※ 2. 汇总区间 &#x1f31f; 3. 矩阵置零 &#x1f31f;&#x1f31f; &#x1f31f; 每日一练刷题专栏 &#x1f31f; Golang每日一练 专栏 Python每日一练 专栏 C/C每日一练 专栏 Java每日一练 专栏 1. 成绩分布 原标题&#xff1a;统计某一单…

基础数据结构-顺序表

顺序表 顺序表定义结构体定义初始化扩容函数打印函数尾插和尾删头插和头删查找函数指定位置插入和删除顺序表销毁 顺序表定义 顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构&#xff0c;一般情况下采用数组存储。在数组上完成数据的增删查改。 顺序表又分为…

图像预处理方法

图像预处理 膨胀腐蚀概述 两个基本的形态学操作是腐 和膨胀。他们 的变体构成了开运算 &#xff0c;闭运算&#xff0c; 梯度等。 根据卷积核的大小前景的所有像素会腐 掉 变为 0 &#xff0c;所以前景物体会变小整幅图像的白色区域会减少。 对于去除白噪声很有用 也可以用来…

推荐系统搭建全程图文攻略

推荐系统搭建全程图文攻略 推荐系统架构简介 整体推荐架构图&#xff1a; 推荐整体从数据处理开始&#xff0c;默认数据从关系型数据到每天增量导入到hive&#xff0c;在hive中通过中间表和调用python文件等一系列操作&#xff0c;将数据处理为算法数学建模的入口数据&#x…

【SVN】在Windows系统上进行SVN的基本操作(检出,更新,提交,分支合并分支,还原,制造冲突以及解决冲突,忽略)

介绍 这里是小编成长之路的历程&#xff0c;也是小编的学习之路。希望和各位大佬们一起成长&#xff01; 以下为小编最喜欢的两句话&#xff1a; 要有最朴素的生活和最遥远的梦想&#xff0c;即使明天天寒地冻&#xff0c;山高水远&#xff0c;路远马亡。 一个人为什么要努力&a…

其实苹果知道自己离不开中国制造,因此悄悄给自己留了后路

苹果在加速离开中国&#xff0c;不过从苹果的做法却又可以看到它其实很清醒地认识到无法离开中国制造&#xff0c;因此它在力推印度制造的时候&#xff0c;其实并没拼尽全力&#xff0c;深刻认识到印度制造和印度市场与中国的差距。 一、印度制造和印度市场与中国的差距 2022年…

防雷知识:什么是雷电浪涌

浪涌是突然发生并超过典型工作电压的过电压。一般来说&#xff0c;浪涌是电路中短暂的电流、电压或功率波。今天我们就来科普一下什么是雷电浪涌。 什么是浪涌&#xff1f; 浪涌&#xff0c;顾名思义&#xff0c;是一种突然发生并超过典型工作电压的过电压。一般来说&#xf…

工具链和其他-异步模块加载

目录 CMD/AMD Asynchronous Module Definition(AMD异步模块定义&#xff0c;语法风格) Common Module Definition ES6/CommonJS CommonJS ES6 Module 加载器示例 总结 cmd和amd的区别 现在有哪些异步加载方式 整体结构 编程&#xff1a;commonjs es6 module (有可能解…

基于STM32和oneNET云平台的数据采集系统(MQTT协议)

文章目录 前言一、onenet云平台产品创建二、硬件选择三、设计理念四、实战编程1. 传感器部分2. ESP82663. 定时器4. 串口5. MQTT 五、进阶练习 前言 该篇为基于stm32esp8266通过mqtt协议连接onenet物联网云平台&#xff0c;单片机部分将采集到的数据(温湿度、光照强度、压强等…

DX云音乐(安卓)

首先&#xff0c;软件安装好不用注册登录就可以直接使用&#xff0c;在首页这里有很多推荐的热门歌单&#xff0c;比如&#xff0c;有年度热门的DJ歌曲&#xff0c;有抖音热门DJ&#xff0c;有各种跨年晚会&#xff0c;有运动必备的DGM&#xff0c;有90后的经典旋律等等。 还有…

php+vue 校友交流平台

1.普通用户功能分析 &#xff08;1&#xff09;用户注册&#xff1a;用于注册校友录用户。 &#xff08;2&#xff09;用户登录&#xff1a;供校友录用户登录。 &#xff08;3&#xff09;资料修改&#xff1a;修改当前登录使用者信息。 &#xff08;4&#xff09;…

MQTT 5协议中的基础更改(一)

01 协议的基础性变化 MQTT 5是对现有协议规范的重大更新&#xff0c;新版本协议具有以下特征&#xff1a;轻量级、易用性、极强的可扩展性、对移动网络的适用性以及通信参与者的解耦。 02 MQTT仍然是MQTT 如果您熟悉MQTT3.1.1&#xff0c;那么您之前知道的关于MQTT的所有原则…