自定义React Hook结合JavaScript Proxy更新状态异常问题排查
React自定义Hook
useProxyState 异常问题分析与修复 问题场景
实现了一个返回Proxy对象的自定义Hook useProxyState,期望通过getter获取状态、setter更新状态。但在组件中使用时出现异常:首次加载后点击按钮修改count.value,页面状态无变化;修改输入框内容触发组件重渲染后,按钮状态才更新,后续点击可正常响应。
原始代码
自定义Hook useProxyState
import { useState } from 'react'; export function useProxyState(initValue: any) { if (typeof initValue !== 'object') initValue = { value: initValue }; const [state, setState] = useState(initValue); const handlers: {} = { get: (target: any, prop: string, reciever: any) => { return state[prop]; }, set: (target: any, prop: string, value: any, reciever: any) => { Reflect.set(target, prop, value, reciever); setState((prevState: any) => (prevState = target)); return true; }, }; return new Proxy(initValue, handlers); }
组件使用代码
import './App.css'; import { useProxyState } from './Hooks/useProxy/useProxyState'; import { useState } from 'react'; export default function App() { const [input, setInput] = useState(''); const count = useProxyState(0); const clickHandler = () => { count.value++; }; return ( <div className='App'> <div className='card'> <button onClick={clickHandler}>count is {count.value}</button> </div> <input value={input} onChange={(e) => setInput(e.target.value)} type='text' /> <div>{input}</div> </div> ); }
异常原因分析
- 状态更新的引用问题:
setState回调中prevState = target是赋值操作,最终返回的target与原state是同一个引用。React的状态更新依赖引用变化检测,当传入的新状态与旧状态引用相同时,React会判定状态未发生改变,不会触发组件重渲染。 - Proxy目标对象的问题:原始代码中Proxy的目标对象是
initValue,该对象在组件首次渲染后会被保留,后续状态更新时始终操作同一个对象,无法触发React的状态变更检测。
修复方案
修复后的useProxyState代码
import { useState } from 'react'; export function useProxyState(obj: any) { if (typeof obj !== 'object') obj = { value: obj }; const [state, setState] = useState(obj); const handlers: {} = { get: (target: any, prop: string, reciever: any) => { return state[prop]; }, set: (target: any, prop: string, value: any, reciever: any) => { Reflect.set(target, prop, value, reciever); setState(target); return true; }, }; let proxy: any = new Proxy({}, handlers); return proxy; }
修复后的组件使用代码
import './App.css'; import { useProxyState } from './Hooks/useProxy/useProxyState'; import { useState } from 'react'; export default function App() { const [input, setInput] = useState(''); let count: any = useProxyState(0); // 初始值为原始类型时,通过 <proxy>.value 访问 const clickHandler = () => { count.value++; console.log(count); }; return ( <div className='App'> <div className='card'> <button onClick={clickHandler}>count is {count.value}</button> </div> <input value={input} onChange={(e) => setInput(e.target.value)} type='text' /> <div>{input}</div> </div> ); }
修复要点
- 将Proxy的目标对象改为空对象
{},避免与初始状态对象产生引用关联; - 修改
setState的调用方式,直接传入更新后的target(此时target已通过Reflect.set更新了属性值),确保每次状态更新传递的是新的状态引用; - 组件中使用时,明确原始类型初始值的访问方式为
proxy.value。
内容的提问来源于stack exchange,提问作者Hunter Woodall
相关产品推荐
相关产品推荐

