当前位置: 首页 > news >正文

02-Hooks完全指南——05-useReducer 与复杂状态

useReducer 与复杂状态

一、useReducer 基础

1.1 什么是 useReducer?

useReducer 是 useState 的替代方案,适用于管理复杂的状态逻辑。它类似于 Redux 的 reducer 模式。

1.2 基本语法

const [state, dispatch] = useReducer(reducer, initialState, init);
  • state:当前状态
  • dispatch:触发状态更新的函数
  • reducer:纯函数,根据 action 返回新状态
  • initialState:初始状态
  • init:惰性初始化函数(可选)

1.3 最简单的例子

// 定义 reducer function counterReducer(state, action) { switch (action.type) { case 'increment': return { count: state.count + 1 }; case 'decrement': return { count: state.count - 1 }; case 'reset': return { count: 0 }; default: return state; } } function Counter() { const [state, dispatch] = useReducer(counterReducer, { count: 0 }); return ( <div> <p>计数: {state.count}</p> <button onClick={() => dispatch({ type: 'increment' })}>+</button> <button onClick={() => dispatch({ type: 'decrement' })}>-</button> <button onClick={() => dispatch({ type: 'reset' })}>重置</button> </div> ); }

二、Reducer 模式详解

2.1 Action 的结构

// Action 通常包含 type 和 payload const action = { type: 'ADD_TODO', payload: { id: 1, text: '学习 React', completed: false } }; // 或者更简洁的写法 dispatch({ type: 'ADD_TODO', text: '学习 React', id: 1 }); // Action 类型使用常量 const ACTIONS = { ADD_TODO: 'ADD_TODO', TOGGLE_TODO: 'TOGGLE_TODO', DELETE_TODO: 'DELETE_TODO' }; dispatch({ type: ACTIONS.ADD_TODO, payload: newTodo });

2.2 完整的 Todo 示例

// 定义 Action 类型 const ACTIONS = { ADD_TODO: 'ADD_TODO', TOGGLE_TODO: 'TOGGLE_TODO', DELETE_TODO: 'DELETE_TODO', EDIT_TODO: 'EDIT_TODO', CLEAR_COMPLETED: 'CLEAR_COMPLETED' }; // Reducer 函数 function todoReducer(state, action) { switch (action.type) { case ACTIONS.ADD_TODO: return { ...state, todos: [...state.todos, action.payload] }; case ACTIONS.TOGGLE_TODO: return { ...state, todos: state.todos.map(todo => todo.id === action.payload ? { ...todo, completed: !todo.completed } : todo ) }; case ACTIONS.DELETE_TODO: return { ...state, todos: state.todos.filter(todo => todo.id !== action.payload) }; case ACTIONS.EDIT_TODO: return { ...state, todos: state.todos.map(todo => todo.id === action.payload.id ? { ...todo, text: action.payload.text } : todo ) }; case ACTIONS.CLEAR_COMPLETED: return { ...state, todos: state.todos.filter(todo => !todo.completed) }; default: return state; } } // 组件 function TodoApp() { const [state, dispatch] = useReducer(todoReducer, { todos: [ { id: 1, text: '学习 React', completed: false }, { id: 2, text: '学习 useReducer', completed: false } ] }); const [inputText, setInputText] = useState(''); const addTodo = () => { if (inputText.trim()) { dispatch({ type: ACTIONS.ADD_TODO, payload: { id: Date.now(), text: inputText, completed: false } }); setInputText(''); } }; return ( <div> <div> <input value={inputText} onChange={(e) => setInputText(e.target.value)} onKeyPress={(e) => e.key === 'Enter' && addTodo()} /> <button onClick={addTodo}>添加</button> </div> <ul> {state.todos.map(todo => ( <li key={todo.id}> <input type="checkbox" checked={todo.completed} onChange={() => dispatch({ type: ACTIONS.TOGGLE_TODO, payload: todo.id })} /> <span style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}> {todo.text} </span> <button onClick={() => dispatch({ type: ACTIONS.DELETE_TODO, payload: todo.id })}> 删除 </button> </li> ))} </ul> <button onClick={() => dispatch({ type: ACTIONS.CLEAR_COMPLETED })}> 清除已完成 </button> </div> ); }

三、惰性初始化

3.1 基础用法

// 初始化函数 function init(initialCount) { // 可以从 localStorage 读取 const saved = localStorage.getItem('count'); return { count: saved ? parseInt(saved) : initialCount }; } function reducer(state, action) { switch (action.type) { case 'increment': return { count: state.count + 1 }; case 'decrement': return { count: state.count - 1 }; default: return state; } } function Counter() { const [state, dispatch] = useReducer(reducer, 0, init); // 保存到 localStorage useEffect(() => { localStorage.setItem('count', state.count); }, [state.count]); return ( <div> <p>计数: {state.count}</p> <button onClick={() => dispatch({ type: 'increment' })}>+</button> <button onClick={() => dispatch({ type: 'decrement' })}>-</button> </div> ); }

3.2 复杂初始化

// 从多个来源初始化状态 function initState(initialState) { const savedState = localStorage.getItem('app-state'); if (savedState) { return JSON.parse(savedState); } // 从 URL 参数读取 const params = new URLSearchParams(window.location.search); const userId = params.get('userId'); return { ...initialState, userId: userId || null }; } function AppReducer(state, action) { // ... } function App() { const [state, dispatch] = useReducer( AppReducer, { user: null, theme: 'light', todos: [] }, initState ); }

四、复杂状态管理

4.1 嵌套状态

const initialState = { user: { profile: { name: '', email: '', avatar: null }, settings: { theme: 'light', notifications: true, language: 'zh-CN' } }, ui: { sidebarOpen: true, modalOpen: false, loading: false } }; function appReducer(state, action) { switch (action.type) { case 'UPDATE_PROFILE': return { ...state, user: { ...state.user, profile: { ...state.user.profile, ...action.payload } } }; case 'UPDATE_SETTINGS': return { ...state, user: { ...state.user, settings: { ...state.user.settings, ...action.payload } } }; case 'TOGGLE_SIDEBAR': return { ...state, ui: { ...state.ui, sidebarOpen: !state.ui.sidebarOpen } }; default: return state; } }

4.2 数组操作

function todoReducer(state, action) { switch (action.type) { // 添加 case 'ADD': return [...state, action.payload]; // 删除 case 'REMOVE': return state.filter(item => item.id !== action.payload); // 更新 case 'UPDATE': return state.map(item => item.id === action.payload.id ? { ...item, ...action.payload.data } : item ); // 批量更新 case 'UPDATE_MANY': return state.map(item => { const update = action.payload.find(u => u.id === item.id); return update ? { ...item, ...update.data } : item; }); // 排序 case 'SORT': return [...state].sort((a, b) => { if (action.payload === 'asc') return a.id - b.id; return b.id - a.id; }); // 过滤 case 'FILTER': return state.filter(item => item.completed === action.payload); default: return state; } }

4.3 异步操作

const initialState = { data: null, loading: false, error: null }; function dataReducer(state, action) { switch (action.type) { case 'FETCH_START': return { ...state, loading: true, error: null }; case 'FETCH_SUCCESS': return { ...state, loading: false, data: action.payload }; case 'FETCH_ERROR': return { ...state, loading: false, error: action.payload }; default: return state; } } function DataFetcher({ url }) { const [state, dispatch] = useReducer(dataReducer, initialState); useEffect(() => { const fetchData = async () => { dispatch({ type: 'FETCH_START' }); try { const response = await fetch(url); const data = await response.json(); dispatch({ type: 'FETCH_SUCCESS', payload: data }); } catch (error) { dispatch({ type: 'FETCH_ERROR', payload: error.message }); } }; fetchData(); }, [url]); if (state.loading) return <div>加载中...</div>; if (state.error) return <div>错误: {state.error}</div>; return <pre>{JSON.stringify(state.data, null, 2)}</pre>; }

五、useReducer vs useState

5.1 何时使用 useReducer

场景推荐原因
简单状态(布尔、数字、字符串)useState代码更简洁
多个相关状态useReducer逻辑集中管理
复杂状态转换useReducerreducer 更清晰
状态依赖之前的状态两者皆可useReducer 更安全
深层嵌套状态useReducer更新逻辑更清晰

5.2 对比示例

// useState 版本 function FormWithState() { const [formData, setFormData] = useState({ name: '', email: '', age: '' }); const updateField = (field, value) => { setFormData(prev => ({ ...prev, [field]: value })); }; const reset = () => { setFormData({ name: '', email: '', age: '' }); }; // ... } // useReducer 版本 const ACTIONS = { UPDATE_FIELD: 'UPDATE_FIELD', RESET: 'RESET' }; function formReducer(state, action) { switch (action.type) { case ACTIONS.UPDATE_FIELD: return { ...state, [action.field]: action.value }; case ACTIONS.RESET: return { name: '', email: '', age: '' }; default: return state; } } function FormWithReducer() { const [formData, dispatch] = useReducer(formReducer, { name: '', email: '', age: '' }); const updateField = (field, value) => { dispatch({ type: ACTIONS.UPDATE_FIELD, field, value }); }; const reset = () => { dispatch({ type: ACTIONS.RESET }); }; }

六、性能优化

6.1 稳定 dispatch

// dispatch 函数是稳定的,不会在重新渲染时改变 function Component() { const [state, dispatch] = useReducer(reducer, initialState); // ✅ dispatch 可以安全地作为依赖 useEffect(() => { dispatch({ type: 'INIT' }); }, [dispatch]); // 不会导致无限循环 }

6.2 拆分 Reducer

// 将大的 reducer 拆分成小的 const userReducer = (state, action) => { switch (action.type) { case 'SET_USER': return { ...state, user: action.payload }; default: return state; } }; const uiReducer = (state, action) => { switch (action.type) { case 'TOGGLE_SIDEBAR': return { ...state, sidebarOpen: !state.sidebarOpen }; default: return state; } }; // 组合 reducer function rootReducer(state, action) { return { user: userReducer(state.user, action), ui: uiReducer(state.ui, action) }; } const initialState = { user: { user: null }, ui: { sidebarOpen: true } };

七、常见陷阱

7.1 直接修改状态

// ❌ 错误:直接修改 function badReducer(state, action) { switch (action.type) { case 'ADD': state.todos.push(action.payload); // 直接修改 return state; } } // ✅ 正确:返回新对象 function goodReducer(state, action) { switch (action.type) { case 'ADD': return { ...state, todos: [...state.todos, action.payload] }; } }

7.2 忘记处理默认情况

// ❌ 错误:忘记返回 state function reducer(state, action) { switch (action.type) { case 'INCREMENT': return { count: state.count + 1 }; // 缺少 default,会返回 undefined } } // ✅ 正确:始终返回 state function reducer(state, action) { switch (action.type) { case 'INCREMENT': return { count: state.count + 1 }; default: return state; // 重要! } }

八、练习题

基础题

  1. 实现一个计数器,支持 +1、-1、重置、步长设置
  2. 实现一个购物车,支持添加、删除、修改数量

进阶题

  1. 实现一个表单状态管理,支持字段验证
  2. 实现一个撤销/重做功能

参考答案

// 购物车 Reducer const cartReducer = (state, action) => { switch (action.type) { case 'ADD_ITEM': { const existing = state.items.find(item => item.id === action.payload.id); if (existing) { return { ...state, items: state.items.map(item => item.id === action.payload.id ? { ...item, quantity: item.quantity + 1 } : item ) }; } return { ...state, items: [...state.items, { ...action.payload, quantity: 1 }] }; } case 'REMOVE_ITEM': return { ...state, items: state.items.filter(item => item.id !== action.payload) }; case 'UPDATE_QUANTITY': return { ...state, items: state.items.map(item => item.id === action.payload.id ? { ...item, quantity: Math.max(0, action.payload.quantity) } : item ).filter(item => item.quantity > 0) }; case 'CLEAR_CART': return { ...state, items: [] }; default: return state; } };

九、小结

要点说明
Reducer纯函数,根据 action 返回新状态
Action包含 type 和 payload 的对象
Dispatch触发状态更新的函数
惰性初始化用于复杂初始状态计算

核心要点:

  • useReducer 适用于复杂状态逻辑
  • Reducer 必须是纯函数
  • 始终返回新对象,不要直接修改
  • 可以配合 Context 实现全局状态管理

http://www.jsqmd.com/news/978519/

相关文章:

  • 从GIS学生到项目实战:我的Cesium 1.91学习笔记与避坑全记录
  • 别再只盯着MobileNet了!手把手教你用PyTorch复现ShuffleNet V2(附完整代码与权重文件)
  • 从MIT Cheetah 3的楼梯测试,聊聊足式机器人‘盲爬’背后的鲁棒性设计
  • 沈阳氦气应用技术要点及合规供应选型指南:沈阳工业气体、沈阳工业氮气、沈阳氧气、沈阳氧气、沈阳氩气、沈阳氮气、沈阳液氮气体选择指南 - 优质品牌商家
  • 别再硬编码了!用SpringBoot优雅地管理阿里云短信模板和签名配置
  • 告别安装报错!Win7/Win10双系统下Qt 5.14.2完整安装与组件选择避坑指南
  • 魔百盒CM301H刷机后体验:当贝桌面+去广告,老盒子300H芯片性能释放实测
  • 模电课设别再头疼了!手把手教你用LM358和滑动变阻器搞定水位检测电路(附完整元器件清单)
  • OneNET MQTT协议上传数据点避坑指南:$dp主题和JSON格式2详解
  • 别再死记硬背了!用‘打电话’和‘寄快递’的故事,5分钟搞懂电路交换和分组交换
  • FIO参数太多看不懂?一张图帮你搞定磁盘性能测试,附送常用场景命令模板
  • 不止于冗余:用锐捷VAC+BFD打造高可用无线网络,一份给运维工程师的配置清单
  • 告别串口打印!用SEGGER RTT调试STM32浮点运算的完整指南(含常见坑点)
  • Java锁机制之park和unpark源码剖析
  • 服务器冗余配置:创建故障转移群集、AlwaysOn、IIS
  • 告别FreeRTOS?在STM32F103上体验微软ThreadX的极简内核与移植心得
  • JWT登录认证系统​ —— 用户注册/登录 + 接口保护
  • 告别命令行恐惧症:用Portainer在5分钟内搞定Docker容器管理(保姆级图文教程)
  • 星悦汇通增强缠绕结构壁管性价比如何 - myqiye
  • 硬件工程师必看:从MII到RGMII,手把手教你搞定以太网PHY与MAC的PCB布局布线(含阻抗控制与等长设计)
  • AI 太阳能智慧灯具高效智能功率 MOSFET 完整选型方案
  • 别再只会用Navicat了!手把手教你用Vue2和Codemirror5.65.2搭建自己的Web版SQL编辑器
  • Windows 下 Claude Code 接入 DeepSeek 与 Cowork 故障排查实录
  • 从‘通道打乱’到‘通道分割’:图解ShuffleNet V1/V2的核心演进与PyTorch实现细节
  • 数据说话:低代码为何能省下七成开发成本
  • 南京口碑好的家电维修培训公司,家洁净教育上榜 - myqiye
  • 别再死磕Pytorch3D官方指南了!我的Linux(Ubuntu 20.04)保姆级安装避坑全记录
  • 科研小白入门:从零开始用NoteExpress管理文献PDF与插入引用(保姆级图文)
  • 技术方案初稿,可以从一次口述开始
  • Winhance中文版:Windows系统优化的终极免费解决方案