众所周知React的useState在set某个值之后虽然视图回刷新,但是无法直接在js中获取到最新的值(更新是异步的),无法实现类似于赋值后回调的操作。但是useRef的特性正好跟useState相反,是可以直接在js中同步更新但是视图不会刷新
简单来说就是组合useState和useRef,制造一个新的hooks来替代useState,在jsx这种照样像useState一样使用,但是在需要做同步代码(回调)的时候使用ref去读取,保证能获取到最新的值。
该方案造成的性能影响未知,但感知上几乎可忽略不计。
直接上代码
import { MutableRefObject, useRef, useState } from "react";
/**
* 定义一个同步状态管理的钩子,用于在React组件中同步管理状态
* 它提供了初始状态设置、状态更新和状态引用的功能
*/
export type UseSyncState = <T = any>(
initialValue: T,
) => [T, (input: T) => void, MutableRefObject<T>];
/**
* 实现useSyncState钩子
* @param initialValue 初始状态值
* @returns 返回一个包含当前状态、更新状态函数和状态引用的数组
*/
export const useSyncState: UseSyncState = <T = any,>(initialValue: T) => {
// 使用useState钩子管理组件的状态
const [state, setState] = useState(initialValue);
// 使用useRef钩子创建一个状态的ref,用于在组件更新时保持对当前状态的引用
const stateRef = useRef<T>(state);
/**
* 同步更新状态的函数,支持传入新的状态值或一个函数,该函数接收上一个状态并返回新的状态
* @param v
*/
const syncSetState: (input: T) => void = (v) => {
setState(v);
stateRef.current = v;
};
// 返回当前状态、更新状态的函数和状态的ref
return [state, syncSetState, stateRef];
};
如何使用
import { useSyncState } from "@/utils/hooks/useSyncState";
import { Button, Space } from "antd";
export const Test: React.FC = () => {
const [count, setCount, countRef] = useSyncState(0);
const handleAddCount = () => {
setCount(countRef.current + 1); //直接能获取到最新的值进行计算
console.log(countRef.current); // 直接能打印出最新的值
};
return (
<div>
<Space>
{/* 视图会显示出最新的值 */}
{count}
<Button onClick={handleAddCount}></Button>
</Space>
</div>
);
};
export default Test;
ps:因为永远可以获取到最新的值,则setState函数不再需要通过函数的方式传入,所以没做这部分兼容。