【Pinia 状态管理篇】Vite + Vue3 组合式 API 模式下的 Store 数据处理

news2024/11/24 11:44:01

文章目录

  • Pinia 状态管理
    • 一、 Pinia 安装与使用
      • 1.1 安装
      • 1.2 注册 pinia 实例到全局
      • 1.3 创建一个 Store
      • 1.4 组件内使用 Store
    • 二、Pinia 核心概念展开学习
      • Store 的定义和使用
      • 2.1 State
      • 2.2 Getter
      • 2.3 Action
    • 附:
      • 1. 什么是魔法字符串?

Pinia 状态管理

在这里插入图片描述

一、 Pinia 安装与使用

Pinia(发音为 /piːnjʌ/) 是一个拥有 “组合式 API” 的 Vue 专属状态管理库,它允许跨组件或页面共享状态。

Pinia 相对比 Vuex 3.x/4.x 发生了哪些变化?

  • mutation 已经被弃用,状态操作更直接
  • 设计上提供一个扁平架构,“不再有可命名模块和嵌套结构模块”
  • API 设计上尽可能地利用 TS 类型推导
  • 无过多的 **”魔法字符串“ 注入,只需要导入函数并调用它们,然后享受自动补全的乐趣就好

1.1 安装

# yarn 方式
yarn add pinia

# npm 方式
npm install pinia

Pinia 默认为 Vite + Vue3 组合式 API 提供量身定制的 Store 状态管理。如果 Vue 版本低于 2.7,请安装组合式 API 包:@vue/composition-api。如果你正在使用 Vue CLI,你可以试试这个非官方插件

1.2 注册 pinia 实例到全局

# main.js
import { createApp } from 'vue'
// 引入 createPinia 函数
import { createPinia } from 'pinia'
import App from './App.vue'

// 创建 pinia 实例(根 store)
const pinia = createPinia()
// 创建应用实例
const app = createApp(App)

// 应用中使用 pinia 插件
app.use(pinia)
// 挂载根组件
app.mount('#app')

Vue2 版本还需要引入插件 PiniaPlugin。

1.3 创建一个 Store

Store(如:Pinia)是一个保存状态和业务逻辑的实体,不与组件树绑定。它承载着全局状态(整个应用中访问的数据),每个引入它的组件都可以对其进行读写操作。

Store 中有三个类似组件的概念:state(data)、getter(computed)、action(methods)。

需要注意的是,并不是所有的数据都要放到 Store 中处理,像购物车、用户登录状态等这些需要快速处理跨组件之间的状态,使用 Store 更加便捷;而像控制元素是否可见这种组件内状态,则不需要 Store 了。

举例描述 Store 管理用户登录状态的过程:

当用户登录时,我们可以在状态管理中记录该用户的信息,以便在后续的用户操作中使用。如果用户在网站上注销,则可以清除该用户的状态。通过使用Pinia,开发人员可以轻松地管理用户的登录状态,并在整个应用程序中验证用户身份。

创建 Store 代码示例(如果没有基础,可以跳过这里):

# src/stores/store-demo.js
import { defineStore } from 'pinia'

const filters = {
    FINISHED: 'finished',
    UNFINISHED: 'unfinished'
}

export const useTodosStore = defineStore('todos', {
    state: () => ({
        // @type {{id: number, text: string,isFinished: boolean}[]}
        todos: [],
        // @type {'all' | 'finished' | 'unfinished'}
        filter: 'all',
        // 类型将自动推断为 number
        id: 0
    }),
    getters: {
        // 过滤列表中自动补全的数据
        finishedTodos(state) {
            return state.todos.filter(todo => todo.isFinished)
        },
        // 过滤列表中非自动补全的数据
        unfinishedTodos(state) {
            return state.todos.filter(todo => !todo.isFinished)
        },
        // 根据 state.filter 结果过滤数据
        // @returns {{ id: number, text: string, isFinished: Boolean }[]}
        filteredTodos(state) {
            console.log(this, this === state) // Proxy(Object) {$id: 'todos', $onAction: ƒ, $patch: ƒ, …} true
            if (this.filter === filters.FINISHED) {
                // 调用其它带有自动补全的 getters 
                return this.finishedTodos
            } else if (this.filter === filters.UNFINISHED) {
                return this.unfinishedTodos
            }
            return this.todos
        }
    },
    actions: {
        // 接受任何数量的参数,返回一个 Promise 或不返回
        addTodo(text) {
            // 状态变更
            this.todos.push({ id: this.id++, text, isFinished: [true, false][parseInt(Math.random() * 2)] })
        }
    }
})

1.4 组件内使用 Store

<script setup>
    import { ref } from 'vue'
    import { useTodosStore } from '@/stores/index'
    const todos = useTodosStore()

    let todoList = ref(todos.todos)

    const add = () => {
        todos.addTodo('测试')
        todoList.value = todos.todos
    }

    const getFinished = () => {
        todoList.value = todos.finishedTodos
    }

    const getUnfinished = () => {
        todoList.value = todos.unfinishedTodos
    }

    const getRanFilter = () => {
        const ranFilter = ['all','finished','unfinished'][parseInt(Math.random() * 3)]
        if (todos.filter === ranFilter) return getRanFilter()
        todos.filter =ranFilter 
        todoList.value = todos.filteredTodos
        console.log(todos.id, todos.filter,todos.todos)
    }
</script>
<template>
    <div class="wrap">
        <button @click="add">添加 todo</button>
        <button @click="getFinished">自动补全</button>
        <button @click="getUnfinished">非自动补全</button>
        <button @click="getRanFilter">随机过滤</button>
        <ul>
            <li 
            v-for="item in todoList"
            :key="item.id">
            	{{ item.text }} ~ {{ item.id }} ~ {{ item.isFinished ? '自动补全' : '非自动补全'}}
            </li>
        </ul>
    </div>
</template>
<style scoped>
    .wrap {
        padding: 20px;
    }
</style>

通过上面演示的代码,对比 Vuex,Pinia 真是太香了!

页面效果:

二、Pinia 核心概念展开学习

Store 的定义和使用

要讲的核心概念其实就是 Store 中的配置信息,而 Store 是通过 defineStore() 来定义的,它接受两个参数:

  • 第一个参数是 Store 的名字,它必须是独一无二的(当前应用中 Store 的唯一 ID,Pinia 将用它来连接 store 和 devtools)
  • 第二个参数是 Store 中的配置信息,它涵盖三个部分:State、Getter、Action。可接受两类值:Setup 函数或 Option 对象

defineStore() 函数执行后的返回值建议以 “use开头”、“store结尾” 的变量进行接收,当然你也可以采用任意你喜欢的方式进行命名。

定义 Store

Option 对象的方式配置:(类似于 Vuex 的写法)

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

import { defineStore } from 'pinia'

export const useCounterStore = defineStore('counter', {
	state: () => ({ count:0 }),
	getters: {
		doubleCount(state){
			return state.count * 2
		}
	},
	actions: {
		increment(){
			this.count++
		}
	}
})
  1. 可以认为state 是 store 的数据 (data),getters 是 store 的计算属性 (computed),而 actions 则是方法 (methods)。

  2. 当前 Store 中,非箭头函数中的 this 等价于 state

Setup 函数的方式:

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

import { ref } frrom 'vue'

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

Setup Store 中:

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

Setup store 比 Option Store 带来了更多的灵活性,因为你可以在一个 store 内创建侦听器,并自由地使用任何组合式函数。但是,使用组合式函数会让 SSR 变得更加复杂。所以在不同的渲染需求下,选择你觉得最舒服的那一种方式定义 Store 就好。

你可以定义任意多的 store,但为了让使用 pinia 的益处最大化(比如允许构建工具自动进行代码分割以及 TypeScript 推断),你应该在不同的文件中去定义 store

**在组件中使用 Store **

<script setup>
	import { useCounterStore } from '@/stores/counter'
	const counterStore = useCounterStore()
	counterStore.increment()
	console.log(counterStore.count)
</script>
<template>
	<div>{{ counterStore.doubleCount }}</div>
</template>

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

store 是一个用 reactive 包装的对象,这意味着不需要在 getters 后面写 .value,就像 setup 中的 props 一样,如果你写了,也不能被解构(破坏了响应性)。

为了从 store 中提取属性时保持其响应性,你可以使用 storeToRefs(),它将为每一个响应式属性创建引用;而 action 则可以直接从 store 中解构。

<script setup>
    import { storeToRefs } from 'pinia'
    const counterStore = useCounterStore()
    const { count, doubleCount } = storeToRefs(counterStore)
    const { increment } = counterStore
</script>

2.1 State

作为 store 的核心,Pinia 将 state 定义为一个返回初始状态的函数。可以同时支持服务端和客户端

import { defineStore } from 'pinia'

const useStore = defineStore('storeId', {
  // 为了完整类型推理,推荐使用箭头函数
  state: () => {
    return {
      // 所有这些属性都将自动推断出它们的类型
      count: 0,
      name: 'Eduardo',
      isAdmin: true,
      items: [],
      hasChanged: true,
    }
  },
})

2.2 Getter

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

export const useStore = defineStore('main', {
  state: () => ({
    count: 0,
  }),
  getters: {
    doubleCount: (state) => state.count * 2,
  },
})

大多数时候,getter 仅依赖 state,不过,有时它们也可能会使用其他 getter。因此,即使在使用常规函数定义 getter 时,我们也可以通过 this 访问到整个 store 实例但(在 TypeScript 中)必须定义返回类型。这是为了避免 TypeScript 的已知缺陷,不过这不影响用箭头函数定义的 getter,也不会影响不使用 this 的 getter

export const useStore = defineStore('main', {
  state: () => ({
    count: 0,
  }),
  getters: {
    // 自动推断出返回类型是一个 number
    doubleCount(state) {
      return state.count * 2
    },
    // 返回类型**必须**明确设置
    doublePlusOne(): number {
      // 整个 store 的 自动补全和类型标注
      return this.doubleCount + 1
    },
  },
})

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

<script setup>
import { useCounterStore } from './counterStore'
const store = useCounterStore()
</script>
<template>
  <p>Double count is {{ store.doubleCount }}</p>
</template>

2.3 Action

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

export const useCounterStore = defineStore('main', {
  state: () => ({
    count: 0,
  }),
  actions: {
    increment() {
      this.count++
    },
    randomizeCounter() {
      this.count = Math.round(100 * Math.random())
    },
  },
})

类似 getter,action 也可通过 this 访问整个 store 实例,并支持完整的类型标注(以及自动补全✨)不同的是,action 可以是异步的,你可以在它们里面 await 调用任何 API,以及其他 action!下面是一个使用 Mande 的例子。请注意,你使用什么库并不重要,只要你得到的是一个Promise,你甚至可以 (在浏览器中) 使用原生 fetch 函数:

import { mande } from 'mande'

const api = mande('/api/users')

export const useUsers = defineStore('users', {
  state: () => ({
    userData: null,
    // ...
  }),

  actions: {
    async registerUser(login, password) {
      try {
        this.userData = await api.post({ login, password })
        showTooltip(`Welcome back ${this.userData.name}!`)
      } catch (error) {
        showTooltip(error)
        // 让表单组件显示错误
        return error
      }
    },
  },
})

你也完全可以自由地设置任何你想要的参数以及返回任何结果。当调用 action 时,一切类型也都是可以被自动推断出来的。

Action 可以像函数或者通常意义上的方法一样被调用:

<script setup>
const store = useCounterStore()
// 将 action 作为 store 的方法进行调用
store.randomizeCounter()
</script>
<template>
  <!-- 即使在模板中也可以 -->
  <button @click="store.randomizeCounter()">Randomize</button>
</template>

附:

1. 什么是魔法字符串?

魔术字符串指的是,在代码之中多次出现、与代码形成强耦合的某一个具体的字符串或者数值。

风格良好的代码,应该尽量消除魔术字符串,改由含义清晰的变量代替。

function btnHandle (type) {
	if (type === 'delete') {
		// 删除操作代码
	}
	// 其它操作判断代码
}

btnHandle('delete')

上述函数中的 ”delete“ 就是魔法字符串。尽管这是一种不太友好的代码书写习惯,但是在日常开发过程中,还是随处可见的。

当字符串与代码融合在一起并出现多次,就与代码形成了 ”强耦合“,这是不利于项目未来的修改和维护的。所以尽量消除这种它,而消除的办法就是把它定义成一个变量。

const DELETE = 'delete'

function btnHandle (type) {
	if (type === DELETE) {
		// 删除操作代码
	}
	// 其它操作判断代码
}

btnHandle(DELETE)

如果同一个操作中,设计多个魔法字符串可以通过对象处理:

const types = {
	ADD: 'add',
	EDIT: 'edit',
	DELETE: 'delete',
	DOWNLOAD: 'download'
}
function btnHandle (type) {
	seitch (type) {
		case types.ADD:
			// 添加操作
			break;
		case types.EDIT:
			// 编辑操作
			break;
		case types.DELETE:
			// 删除操作
			break;
		case types.DOWNLOAD:
			// 下载操作
			break;
	}
}

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

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

相关文章

Nacos配置中心使用(Spring Cloud版)

目标 向项目中集成Nacos配置。原项目是一个SpringBoot项目。这里假设我们无法修改原有项目的SpringBoot版本。 注意 在不动SpringBoot版本的前提下&#xff0c;根据SpringBoot的版本&#xff0c;确定Spring Cloud和Nacos版本。Nacos版本其实就是Spring Cloud Alibaba版本。在…

【Linux】网络结构模式

目录 网络结构模式C/S结构B/S结构 MAC地址IP地址端口网络模型OSI七层模型TCP/IP四层模型 通信过程数据包封装协议以太网协议ARP协议IP数据报格式UDP协议格式TCP协议格式封装分用TCP详解TCP和UDPTCP通信流程TCP三次握手 网络结构模式 C/S结构 客户机-服务器(client-server)结构…

uboot读取adc,通过cmdline传给kernel解析数值,不同硬件模块进行处理

uboot读取adc&#xff0c;通过cmdline传给kernel解析数值&#xff0c;不同硬件模块进行处理 uboot开发&#xff0c;要想读取adc在哪个时间点读取以及如何传给cmdline&#xff0c;需要清楚2点&#xff0c; uboot启动得大致流程及相关启动功能&#xff0c;uboot各个硬件模块得接…

主机安全各个方面应该怎么做?

主机安全是指保护主机系统不受恶意攻击和未经授权访问的威胁&#xff0c;以保证系统的稳定性、可用性和数据的安全性。 以下是主机安全涉及的几个方面做法&#xff1a; 1. 操作系统安全&#xff1a;安装操作系统时需要选择合适的版本&#xff0c;及时打补丁更新&#xff0c;配置…

入职Linux驱动工程师后,我才知道的真相…

大家好&#xff0c;我是ST。 做Linux驱动工程师也有一段时间了&#xff0c;今天分享一下我曾经入职才知道的一些事情&#xff0c;算是一个菜鸟的经历吧&#xff01; 设备树 起初学习Linux驱动&#xff0c;是从最简单的一个.c文件开始。 在.c中实现module_init和module_exit这…

大模型入门(三)—— 大模型的训练方法

参考hugging face的文档介绍&#xff1a;https://huggingface.co/docs/transformers/perf_train_gpu_many#naive-model-parallelism-vertical-and-pipeline-parallelism&#xff0c;以下介绍聚焦在pytorch的实现上。 随着现在的模型越来越大&#xff0c;训练数据越来越多时&…

基于Ubuntu22.10系统安装部署webmin软件

Webmin是一个用于Linux系统管理的开源的基于web的系统管理配置工具。有了这个工具的帮助&#xff0c;我们可以管理内部的系统配置&#xff0c;诸如设置用户账户&#xff0c;磁盘配额&#xff0c;像Apache, DNS, PHP, MySQL&#xff0c;文件共享的服务等。 本文描述在ubuntu22.…

VS+C#+WPF多线程视频摄像头播放器监控

程序示例精选 C#WPF多线程视频摄像头播放器监控 如需安装运行环境或远程调试&#xff0c;见文章底部个人QQ名片&#xff0c;由专业技术人员远程协助&#xff01; 前言 这篇博客针对<<C#WPF多线程视频摄像头播放器监控>>编写代码&#xff0c;代码整洁&#xff0c;规…

el-table点击单元格变成输入框,以及其自动获取焦点失效可能的原因(focus失效)

1.el-table点击单元格变成输入框 这里主要使用了el-table三个自带的方法/属性&#xff1a; <el-table:data"MesTableData"bordercell-click"clickCell":row-class-name"tableRowClassName":cell-class-name"tableCellClassName" …

解决elementUI弹出框关闭后再打开el-select下拉框无法选中的问题

文章目录 一、问题描述&#xff1a;二、问题解决 一、问题描述&#xff1a; 使用的前端UI框架为elementUI。 el-select组件在一个弹框中&#xff0c;打开该弹框&#xff0c;el-select可以正常选中&#xff0c;但是保存弹框中的表单信息关闭弹框后&#xff0c;再打开弹框&…

RT-DETR论文解读与代码

1.概述 目前以大名鼎鼎的YOLO为代表的基于CNN的实时监测网络需要NMS进行后处理&#xff0c;导致不能很好的优化网络&#xff0c;并且网络不够健壮&#xff0c;从而导致检测器的推理速度出现延迟。研究者也分析了Anchor-based和Anchor-free的YOLO的性能&#xff0c;发现Anchor并…

【Java基础篇】方法的使用(方法的重载和递归)

作者简介&#xff1a; 辭七七&#xff0c;目前大一&#xff0c;正在学习C/C&#xff0c;Java&#xff0c;Python等 作者主页&#xff1a; 七七的个人主页 文章收录专栏&#xff1a;Java.SE&#xff0c;本专栏主要讲解运算符&#xff0c;程序逻辑控制&#xff0c;方法的使用&…

打造智能生活方式

2个互联网工具与你分享 分享一&#xff1a; 随记单词是一款功能强大的单词记忆和管理应用程序。它为用户提供了便捷的学习工具和智能化的记忆方式&#xff0c;帮助用户轻松有效地记忆和掌握单词。 随记单词的特点之一是个性化记忆计划。用户可以根据自己的学习进度和需求&am…

如何使用MATLAB处理涡度通量数据

MATLAB MATLAB是美国MathWorks公司出品的商业数学软件&#xff0c;用于数据分析、无线通信、深度学习、图像处理与计算机视觉、信号处理、量化金融与风险管理、机器人&#xff0c;控制系统等领域。 MATLAB是matrix&laboratory两个词的组合&#xff0c;意为矩阵工厂&#x…

苹果头显Vision Pro深度解读3 苹果头显visonOS开发指南

1 程序员visonOS开发指南 作为iOS开发者&#xff0c;切换到visionOS开发非常简单啊&#xff0c;过去的一些技术基本上都用得上。目前根据苹果WWDC官方的文档&#xff0c;视频&#xff0c;我们可以知道: 开发语言&#xff0c;使用的是swift object-c c c等&#xff0c;swif…

简化日志数据管理:利用 Elastic 灵活路由的力量

作者&#xff1a;Felix Barnsteiner&#xff0c;Nicolas Ruflin 在 Elasticsearch 8.8 中&#xff0c;我们在技术预览中引入了重新路由处理器&#xff08;reroute processor&#xff09;&#xff0c;它可以根据灵活的路由规则将文档&#xff08;例如日志&#xff09;发送到不同…

Jmeter吞吐量控制器使用小结

吞吐量控制器(Throughput Controller)场景: 在同一个线程组里, 有10个并发, 7个做A业务, 3个做B业务,要模拟这种场景,可以通过吞吐量模拟器来实现.。 添加吞吐量控制器 如果你想学习jmeter性能测试&#xff0c;我这边给你推荐一套视频&#xff0c;这个视频可以说是B站播放全网…

第十六届全国大学生信息安全竞赛CISCN---Cypto

1.Sign_in_passwd 打开环境&#xff0c;下载附件&#xff0c;用记事本打开flag的文件 发现里面是像base密码但又不像base密码的一串&#xff0c;第二行的%&#xff0c;就很像url密码啊&#xff0c;之前做题没接触过啊&#xff0c;还是做题做少了 没有啥解题思路&#xff0c;看…

基于Jeecg-boot的flowable流程支持拒绝同意流程操作

更多功能看演示系统 gitee源代码地址 后端代码&#xff1a; https://gitee.com/nbacheng/nbcio-boot 前端代码&#xff1a;https://gitee.com/nbacheng/nbcio-vue.git 在线演示&#xff08;包括H5&#xff09; &#xff1a; http://122.227.135.243:9888 因为看很多朋友需要…

利用Transformer BEV解决自动驾驶Corner Case的技术原理

摘要&#xff1a; 本文总结了Transformer和BEV技术在自动驾驶中的原理和应用&#xff0c;特别是如何解决Corner Case问题。 自动驾驶系统在实际应用中需要面对各种复杂的场景&#xff0c;尤其是Corner Case&#xff08;极端情况&#xff09;对自动驾驶的感知和决策能力提出了更…