React--》掌握Valtio让状态管理变得轻松优雅

Valtio采用了代理模式,使状态管理变得更加直观和易于使用,同时能够与React等框架无缝集成,本文将深入探讨Valtio的核心概念、使用场景以及其在提升应用性能中的重要作用,帮助你掌握这一强大工具,从而提升开发效率和用户体验。

目录

初识Valtio

Valtio基础使用

代理与快照

订阅与侦听

历史与对象


初识Valtio

Valtio是一个轻量级的状态管理库,专为现代前端框架react设计,它使用JS的代理(Proxy)特性,提供了一种简单而高效的方式来管理和共享状态,其官方网址为:地址 ,如下所示:

Proxy概念:是一种用于拦截和定义基本操作(如属性查找、赋值、枚举、函数调用等)的对象,它可以在JS中通过Proxy构造函数创建。可以理解成在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。其优势如下:

1)可以监听整个对象而非属性及监听数组的变化

2)不改变语句语法,实现额外的操作或守卫功能。

3)返回的是一个新对象,可以只操作新的对象达到目的

4)增强了js的元编程能力,方便实现各种内部DSL。

使用Valtio理由:因为其允许开发者轻松地创建可响应的状态对象,使得状态变化能够自动触发UI更新,这使得开发者能够更方便地管理应用中的状态,同时保持代码的简洁和可读性,其具备的优势如下所示:

1)简单易用:Valtio 的 API 直观,学习曲线平缓,开发者可以快速上手。

2)高性能:通过使用代理,Valtio 只会对变化的部分进行更新,避免了不必要的重渲染,提高了性能。

3)无缝集成:与 React 和其他框架的集成非常顺畅,允许开发者在熟悉的环境中使用。

4)小巧轻便:Valtio 的体积小,依赖少,不会给项目带来额外负担。

5)可组合性:可以与其他状态管理工具(如 Zustand 或 Redux)结合使用,增强灵活性。

valtio使用:具有较少的api,核心就两个proxy与useProx,如下所示:

1)proxy:用于包装原始对象,生成可监听修改对象状态方法也就是可修改的状态state,组件中使用操作状态的函数来生成渲染用的snapshot来反映到原始对象上。

2)useProxy:用于返回每次渲染间的不可修改对象snapshot,相当于state的一份拷贝snapshot仅用于视图的展示。

state与snapshot都进行了Proxy处理,state的代理会修改原始对象,snapshot的代理则会用于记录引用类型使用情况,接下来我们通过代码进行一个演示,终端执行如下命令对Valtio进行一个安装:

npm i valtio

下面这段代码创建了一个简单的状态管理对象,允许通过addCount函数来更新count值,使用其代理功能可以确保当count变化时任何依赖于该值的 UI 组件都会自动更新,我们用proxy包裹一个状态,当然在其中也可以定义许多其他状态,如下所示:

import { proxy } from "valtio";type IStore = {count: number
}const store = proxy<IStore>({count: 1
})
export const addCount = (num: number) => {store.count += num
}
export default store

在根组件中我使用Valtio的useSnapshot解构快照,组件会在count改变时自动更新从而实现了简单的状态管理和UI交互,如下所示:

import { useSnapshot } from "valtio";
import store, { addCount } from "./store";const App = () => {const { count } = useSnapshot(store);return (<><h1>计数器 -- {count}</h1><button onClick={() => addCount(1)}>改变</button></>)
}export default App;

最终呈现的效果如下所示:

Valtio基础使用

由于Valtio采用了基于 Proxy 的设计理念使得状态管理变得轻量且高效,通过它开发者能够轻松创建响应式状态实时更新 UI,而无需复杂的配置或冗长的代码,接下来我们开始对其进行一个简单使用的介绍

代理与快照

proxy代理:当我们使用代理proxy时需要注意以下几个方面:

proxy会跟踪对原始对象和所有嵌套对象的更改,并在修改对象时通知侦听器:

import { proxy } from 'valtio'const state = proxy({ count: 0, text: 'hello' })
const personState = proxy({ name: 'Timo', role: 'admin' })
const authState = proxy({ status: 'loggedIn', user: personState }) // 嵌套代理// 可以像普通js一样对proxy对象中的属性进行操作
setInterval(() => {++state.count
}, 1000)
// 代理可以嵌套在其他proxy对象中并作为一个整体进行更新
authState.user.name = 'Nina'

proxy可以对代理的属性值进行异步操作,并可以接收多种格式数据的代理:

import { proxy } from 'valtio'// 异步代理
const bombState = proxy({explosion: new Promise((resolve) => setTimeout(() => resolve('Boom!'), 3000)),
})
// 代理对象可以包含任何类型的值,包括函数
const state = proxy({chart: d3.select('#chart'),component: React.createElement('div'),map: new Map(), // see proxyMapstorage: localStorage,
})

如果不想被proxy代理又想取值,可以使用ref进行包裹:

import { proxy, ref } from 'valtio'const state = proxy({count: 0,dom: ref(document.body),
})

useSnapshot快照:创建捕获更改的本地snapshot,将Valtio快照包装在访问跟踪代理中确保组件渲染优化,即只有当它(或其子组件)专门访问的键发生更改时它才会重新渲染,而不是在代理的每一次更改时:

在组件渲染中读取快照,在valtio回调中使用代理。快照是只读的,进行任何读取修改等操作都需要通过代理进行以便回调读取和写入最新值:

useSnapshot依赖于子代理的原始引用,所以如果用新的引用替换它,组件 订阅旧代理的不会收到新的更新,因为它仍然订阅旧代理,可以使用下面方法,不需要担心重新渲染因为它是渲染优化的:

const snap = useSnapshot(state)
return <div>{snap.profile.name}</div>const { profile } = useSnapshot(state)
return <div>{profile.name}</div>

订阅与侦听

subscribe订阅:可以访问组件外部的状态并订阅更改

Valtio中的subscribe函数,可以在任何组件中如果proxy中的对象发生变化都会被执行,作用是为整个代理对象添加订阅功能,当代理对象的任何部分发生变化时,注册的回调函数都会被触发,这使得开发者可以监控整个状态的变化,并在变化时执行特定的逻辑:

subscribeKey函数是为特定的状态键添加订阅功能,当该键的值发生变化时注册的回调函数会被触发,这使得开发者能够精确地响应状态变化从而实现更细粒度的更新和优化性能:

import { useEffect } from "react";
import { useSnapshot } from "valtio";
import { subscribeKey } from "valtio/utils";
import store, { addCount } from "./store";const App = () => {const { count } = useSnapshot(store);useEffect(() => {const unsubscribe = subscribeKey(store, 'count', (v) =>console.log('state has changed to', v),)return () => unsubscribe()}, [])return (<><h1>计数器 -- {count}</h1><button onClick={() => addCount(1)}>改变</button></>)
}export default App;

watch侦听:与只侦听单个代理的subscribe不同,watch支持订阅多个代理对象,对代理对象(或其子代理)的任何更改都将重新运行回调:

用于监控代理状态的变化并在状态发生变化时执行特定的回调函数,与subscribe不同watch主要用于观察特定的状态部分,可以更精确地响应变化:

import { useSnapshot } from "valtio";
import { watch } from 'valtio/utils'
import store, { addCount } from "./store";
import { useEffect } from "react";const App = () => {const { count } = useSnapshot(store);useEffect(() => {const stop = watch((get) => {console.log("state has changed to", get(store).count);})return () => stop();}, [])return (<><h1>计数器 -- {count}</h1><button onClick={() => addCount(1)}>改变</button></>)
}export default App;

历史与对象

proxyWithHistory历史:终端执行 npm i valtio-history 按照该插件,可以使用proxyWithHistory实用函数用于创建具有快照历史记录的代理。

创建一个具有历史记录功能的代理对象。这意味着你可以追踪状态的变化,记录每次修改的历史,并允许你在需要时恢复到之前的状态:

import "./App.css";
import { useSnapshot } from "valtio";
import { proxyWithHistory } from "valtio-history";
import React from "react";const textProxy = proxyWithHistory({text: "Add some text to this initial value and then undo/redo",
});
const update = (event: React.ChangeEvent<HTMLTextAreaElement>) =>(textProxy.value.text = event.target.value);export default function App() {const { value, undo, redo, history, canUndo, canRedo, getCurrentChangeDate } = useSnapshot(textProxy);return (<div className="App"><h2>Editor with history</h2><div className="info"><span>change {history.index + 1} / {history.nodes.length}</span><span>|</span><span>{getCurrentChangeDate().toISOString()}</span></div><div className="editor"><textarea value={value.text} rows={4} onChange={update} /></div><button onClick={undo} disabled={!canUndo()}>Undo</button><button onClick={redo} disabled={!canRedo()}>Redo</button></div>);
}

proxySet与proxyMap操作:两个函数用于创建特定类型的代理对象,以便更方便地管理集合数据结构:

proxySet:用于创建一个代理对象,表示一个集合(Set)。它允许你跟踪集合中的元素变化,比如添加、删除等。

import { proxySet } from 'valtio/utils'// 创建一个代理集合
const mySet = proxySet(new Set());
// 添加元素
mySet.add('item1');
mySet.add('item2');
// 监听集合变化
console.log([...mySet]); // 输出: ['item1', 'item2']
// 删除元素
mySet.delete('item1');
console.log([...mySet]); // 输出: ['item2']

proxyMap:用于创建一个代理对象表示一个映射(Map),它可以跟踪键值对的变化,例如添加、删除或更新键值对。

import { proxyMap } from 'valtio/utils';// 创建一个代理映射
const myMap = proxyMap(new Map());
// 添加键值对
myMap.set('key1', 'value1');
myMap.set('key2', 'value2');
// 监听映射变化
console.log([...myMap.entries()]); // 输出: [['key1', 'value1'], ['key2', 'value2']]
// 更新键值对
myMap.set('key1', 'updatedValue');
console.log(myMap.get('key1')); // 输出: 'updatedValue'
// 删除键值对
myMap.delete('key2');
console.log([...myMap.entries()]); // 输出: [['key1', 'updatedValue']]

总结:通过本文的探讨,我们可以看到Valtio在状态管理方面的强大与灵活性。它的代理机制使得响应式编程变得简单,其设计不仅提高了开发效率,还促进了代码的可维护性,无论是小型项目还是大型应用,Valtio都为前端开发者提供了一个高效而直观的解决方案,是现代状态管理的理想选择。

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

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

相关文章

【Go语言】

type关键字的用法 定义结构体定义接口定义类型别名类型定义类型判断 别名实际上是为了更好地理解代码/ 这里要分点进行记录 使用传值的例子&#xff0c;当两个类型不一样需要进行类型转换 type Myint int // 自定义类型&#xff0c;基于已有的类型自定义一个类型type Myin…

用kali入侵 DarkHole_2测试

进入kali系统调出root交互式界面 netdiscover -r 000.000.000.000/24 -------局域网探测IP工具 nmap 设备端口扫描 发现两个攻击点一个是80端口的Http 一个是22端口的ssh 发现有许多GIT文件 可能会出现git源码泄露 使用githack URL 命令还原git源文件 打开面板控制命令行 输入…

2.插入排序(斗地主起牌)

一、思想 扑克牌起牌 代码&#xff1a; 二、时间复杂度&#xff1a; 最好情况&#xff08;已经排序好的&#xff09;&#xff1a;T O(N) 最坏情况&#xff08;完全逆序&#xff09;&#xff1a;T O(N^2) 三、优劣&#xff1a; 严格的大小比较之后才进行错位插入&#x…

unity中GameObject介绍

在 Unity 中&#xff0c;Cube和Sphere等基本几何体是 Unity 引擎的内置预制体&#xff08;Prefabs&#xff09;&#xff0c;它们属于 Unity 中的GameObject 系统&#xff0c;可以在 Unity 的 Hierarchy 视图或 Scene 视图中右键点击&#xff0c;然后在弹出的菜单中选择 3D Obje…

JVM参数选项类型

我的后端学习大纲 JVM学习大纲 1、类型1&#xff1a;标准参数选项&#xff1a; 1.1.特点&#xff1a; 1.比较稳定&#xff0c;后续基本不会发生变化2.以“-”开头 1.2.各种选项&#xff1a; 运行java或者java -help可以看到所有的标准选项 1.3.补充内容&#xff1a; -se…

Halcon 多相机统一坐标系(标定)

多相机统一坐标系是指将多个不同位置的相机的图像采集到同一个坐标系下进行处理和分析的方法。 在计算机视觉和机器视觉领域中&#xff0c;多相机统一坐标系被广泛应用于三维重建、立体视觉、目标跟踪等任务中。 以gen_binocular_rectification_map&#xff08;生成描述图像映…

Android View的事件分发机制

前言 本文由于介绍本人关于View的事件分发机制的学习&#xff0c;如有不恰当的描述欢迎指出。 View基础 什么是View ​ View是Android中所有控件的基类&#xff0c;不管是Button、TextView、LinearLayout&#xff0c;它们的共同基类都是View。也就是说&#xff0c;View是界…

K8S配置storage-class

简介 Kubernetes支持NFS存储&#xff0c;需要安装nfs-subdir-external-provisioner&#xff0c;它是一个存储资源自动调配器&#xff0c;它可将现有的NFS服务器通过持久卷声明来支持Kubernetes持久卷的动态分配。该组件是对Kubernetes NFS-Client Provisioner的扩展&#xff0…

腾讯云跨AZ部署FortigateHA备忘录

随时保存配置 config system globalset admintimeout 480set alias "FortiGate-VM64-KVM"set gui-auto-upgrade-setup-warning disableset hostname "FG-Slave"set revision-backup-on-logout enableset revision-image-auto-backup enableset timezone &…

面向对象与设计模式第一节:深入理解OOP

第三章&#xff1a;面向对象与设计模式 第一节&#xff1a;深入理解OOP 面向对象编程&#xff08;OOP&#xff09;是一种编程范式&#xff0c;它将程序结构视为由对象组成&#xff0c;促进了代码的重用性和可维护性。在这一课中&#xff0c;我们将深入分析OOP的四个基本特性&…

[JAVAEE] 多线程的案例(三) - 线程池

目录 一. 什么是线程池 二. 线程池的作用 三. java提供的线程池类 四. ThreadPoolExecutor的构造方法及参数理解 1. int corePoolSize: 核心线程数. 2. int maximumPoolSize: 最大线程数 核心线程数 非核心线程数 3. int keepAliveTime:非核心线程允许空闲的最大时间. …

DataX简介及使用

目录 一、DataX离线同步工具DataX3.0介绍 1.1、 DataX 3.0概览 1.2、特征 1.3、DataX3.0框架设计 1.4、支持的数据元 1.5、DataX3.0核心架构 1.6、DataX 3.0六大核心优势 1.6.1、可靠的数据质量监控 1.6.2、丰富的数据转换功能 1.6.3、精准的速度控制 1.6.4、强劲的…

正则表达式和通配符

文章目录 正则表达式和通配符的区别正则表达式&#xff08;Regex&#xff09;通配符&#xff08;Wildcards&#xff09;总结 正则表达式的概念正则表达式的由来为什么要使用正则表达式 正则表达式的语法组成修饰符元字符\f\b\B 在Linux中的基础正则和扩展正则基础正则(BRE)^$.*…

面试时被问到“Scaling Law”,该怎么答?

在大模型的研发中&#xff0c;通常会有下面一些需求&#xff1a; 计划训练一个 10B 的模型&#xff0c;想知道至少需要多大的数据&#xff1f; 收集到了 1T 的数据&#xff0c;想知道能训练一个多大的模型&#xff1f; 老板准备 1 个月后开发布会&#xff0c;给的资源是 100 …

Linux安装Nginx教程(rpm安装方式)

本章教程,主要介绍如何在Linux Centos7系统上,使用rpm的方式进行安装Nginx。 一、安装wget插件 如果不存在wget下载插件,需要安装一下。 yum install -y wget二 、下载rpm安装包 官方提供的rpm下载地址:https://nginx.org/packages/centos/7/x86_64/RPMS/ <

【Nginx系列】499错误

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

Postman常见问题及解决方(全)

&#x1f345; 点击文末小卡片 &#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 1、网络连接问题 如果Postman无法发送请求或接收响应&#xff0c;可以尝试以下操作&#xff1a; 检查网络连接是否正常&#xff0c;包括检查网络设置、代理设置…

软考中级嵌入式系统设计师笔记分享(二)

1.TTL 电路是电流控制器件&#xff0c;而CMOS 电路是电压控制器件。 2.TTL 电路的速度快&#xff0c;传输延迟时间短(5-10ns)&#xff0c;但是功耗大。 常见的串行总线有 SPI、II2C、USB、RS232/RS422/RS485、CAN等;高速串行总线主要有 SATA、PCIE、IEEE 1394、Rapidl0、USB 3…

1.DBeaver连接hive数据库

1.hive开启远程服务&#xff0c;linux中直接输入&#xff1a;hiveserver2 2.解压dbeaver和hive-jdbc-2.1.1.zip 3.双击打开 4.数据库&#xff0c;新建连接 5.搜索hive 6.配置参数 7.编辑驱动设置 8.添加jar包 9.测试连接 10.右击&#xff0c;新建sql编辑器 11.执行sql 12.调整字…

【每日一题】LeetCode - 整数转罗马数字

在罗马数字系统中&#xff0c;七个不同的符号代表不同的值&#xff1a; 符号值I1V5X10L50C100D500M1000 罗马数字的表示方式是从最大值开始逐次减去每个符号的值&#xff0c;通过组合这些符号构建最终的表示形式。本文将介绍一个基于贪心策略的解决方案&#xff0c;将整数转换…