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…

exchange_proxy exchange 安全代理

1. 软件简介 exchange_proxy 是由小米公司开发并开源的,以 go 语言开发的 exchange 安全代理,可以将内网的 exchange 服务器的 https 服务安全地发布出去, 支持的功能如下: WEB 端增加 OTP 二次认证手机端增加设备激活绑定的功能屏蔽了 PC 端的 EWS 协议(意思就是不支持)…

gin入门教程(5):请求参数处理

在 Gin 中&#xff0c;处理请求参数非常简单。您可以从 URL 路由、查询字符串和请求体中提取参数。以下是几种常见的处理方式&#xff1a; 1. URL 路由参数 如果您想从 URL 中获取参数&#xff0c;可以使用路由定义中的冒号&#xff08;:&#xff09;符号&#xff1a; r.GET…

【PHP】在ThinkPHP6中Swoole与FPM的简单性能测试对比

一、前言 本文主要测试在ThinkPHP 6框架中,使用Swoole扩展库与使用PHP-FPM两者的HTTP并发性能差距,测试方法较简单,仅供参考。 二、测试环境 系统:Ubuntu 22.04 PHP版本:7.4.33 Swoole版本:4.8.13 ThinkPHP版本:6.1.5 ThinkPHP-Swoole扩展库版本:3.1.4 测试工具:A…

unity中GameObject介绍

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

MySQLDBA修炼之道-开发篇(一)

三、开发基础 1. 数据模型 1.1 关系数据模型介绍 关于NULL 如果某个字段的值是未知的或未定义的&#xff0c;数据库会提供一个特殊的值NULL来表示。NULL值很特殊&#xff0c;在关系数据库中应该小心处理。例如查询语句“select*from employee where 绩效得分<85 or>绩…

ElasticSearch的向量存储和搜索

ElasticSearch的向量存储和搜索 引入依赖示例代码 引入依赖 <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schema…

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;生成描述图像映…

【NodeJS】NodeJS+mongoDB在线版开发简单RestfulAPI (七):MongoDB的设置

本项目旨在学习如何快速使用 nodejs 开发后端api&#xff0c;并为以后开展其他项目的开启提供简易的后端模版。&#xff08;非后端工程师&#xff09; 由于文档是代码写完之后&#xff0c;为了记录项目中需要注意的技术点&#xff0c;因此文档的叙述方式并非开发顺序&#xff0…

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

179.最大数

目录 题目解法sort可以自定义排序规则 题目 给定一组非负整数 nums&#xff0c;重新排列每个数的顺序&#xff08;每个数不可拆分&#xff09;使之组成一个最大的整数。 注意&#xff1a;输出结果可能非常大&#xff0c;所以你需要返回一个字符串而不是整数。 解法 class S…

Android开发兼容性问题3万字保姆级教程(Android版本、屏幕、多语言、硬件、第三方库、权限)

目录 第一章 Android版本兼容性 1.1 版本众多的挑战 1.2 设置版本参数 1.3 API版本检测 1.4 兼容性实例 使用minSdkVersion和targetSdkVersion 1.5 版本更新的应对策略 第二章 屏幕尺寸与分辨率兼容性 2.1 屏幕尺寸的多样性 2.2 响应式布局 2.3 drawable资源管理 使…

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

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

Flutter鸿蒙next 布局架构原理详解

✅近期推荐&#xff1a;求职神器 https://bbs.csdn.net/topics/619384540 &#x1f525;欢迎大家订阅系列专栏&#xff1a;flutter_鸿蒙next &#x1f4ac;淼学派语录&#xff1a;只有不断的否认自己和肯定自己&#xff0c;才能走出弯曲不平的泥泞路&#xff0c;因为平坦的大路…

【mysql】转义字符反斜杠,正则表达式

1.mysql插入 1.1插入-反斜杠规则&#xff1a; ​ 在MySQL中&#xff0c;反斜杠在字符串中是属于转义字符&#xff0c;经过语法解析器解析时会进行一次转义&#xff0c;所以当我们insert反斜杠&#xff08;\&#xff09;字符时&#xff0c;如 insert “\” 在数据库中最…