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都为前端开发者提供了一个高效而直观的解决方案,是现代状态管理的理想选择。