使用useReducer出现更新深度超限问题,useState则正常
问题描述
我有一个监听输入框内容的useEffect,一开始用useState实现时运行流畅,但改成useReducer后出现异常——即使用户没有输入内容,useEffect也会持续触发,而useState版本没有这个问题。
相关代码
useReducer版本的useEffect
useEffect(() => { dispatch({type : 'username', payload : watch().username}); dispatch({type : 'password', payload : watch().password}); console.log(state.username, state.password); }, [watch()]);
useState版本的useEffect
useEffect(() => { setPassword(watch().password); setUsername(watch().username); console.log(username, password); }, [watch()]);
reducer实现
const reducer = (state, action) => { switch (action.type) { case "username": return {...state, username: action.payload }; case "password": return {...state, password : action.payload}; } };
问题原因
- 依赖项引用变化:
watch()(大概率是react-hook-form这类表单库的方法)每次调用都会返回新的对象引用,哪怕表单值没变化,useEffect检测到依赖更新就会触发。 - useReducer的无意义更新:React的
useState会自动跳过值未变化的状态更新,但你的reducer每次都返回新的{...state}对象——哪怕payload和当前状态值完全一致,React也会认为状态已更新,触发组件重渲染,进而再次执行useEffect,形成循环触发。
解决方案
1. 给reducer加值判断,避免无意义更新
在reducer里先判断payload是否和当前状态值一致,一致就返回原state,不生成新对象:
const reducer = (state, action) => { switch (action.type) { case "username": if (state.username === action.payload) return state; return {...state, username: action.payload }; case "password": if (state.password === action.payload) return state; return {...state, password : action.payload}; default: return state; // 必须加default分支,防止返回undefined } };
2. 优化useEffect的依赖项
不要直接把watch()返回的整个对象当依赖,改成依赖具体的字段,或者用useMemo缓存结果:
// 方式一:依赖具体字段 const { username, password } = watch(); useEffect(() => { dispatch({type : 'username', payload : username}); dispatch({type : 'password', payload : password}); console.log(state.username, state.password); }, [username, password]); // 方式二:用useMemo缓存watch结果 const formValues = useMemo(() => watch(), [watch]); useEffect(() => { dispatch({type : 'username', payload : formValues.username}); dispatch({type : 'password', payload : formValues.password}); console.log(state.username, state.password); }, [formValues]);
3. 合并dispatch操作
没必要分两次dispatch,一次更新多个字段,减少状态更新次数:
const { username, password } = watch(); useEffect(() => { dispatch({ type: 'updateForm', payload: { username, password } }); console.log(state.username, state.password); }, [username, password]); // 对应更新reducer const reducer = (state, action) => { switch (action.type) { case "updateForm": const { username, password } = action.payload; if (state.username === username && state.password === password) { return state; } return {...state, username, password }; case "username": if (state.username === action.payload) return state; return {...state, username: action.payload }; case "password": if (state.password === action.payload) return state; return {...state, password : action.payload}; default: return state; } };
内容的提问来源于stack exchange,提问作者Metgher Andrei
相关产品推荐
相关产品推荐

