我学 React Hooks 的笔记:useState、useEffect、useContext 用法与踩坑

发布时间:2026/6/15 6:39:07

我学 React Hooks 的笔记:useState、useEffect、useContext 用法与踩坑 我学习 React Hooks 的笔记useState、useEffect、useContext大家好我刚开始学 React 不久最近终于搞懂了最常用的三个 Hooks —— useState、useEffect 和 useContext。今天把我自己的学习笔记和踩坑经验分享出来希望能帮到跟我一样的初学者。以前写类组件总觉得好麻烦又要 constructor 又要 this一不小心 this 指向就错了。后来知道函数组件加上 Hooks 就能做同样的事代码还更短我就开始认真学 Hooks。下面一个一个说。一、useState —— 给函数组件一个“记忆”1. 怎么用最简单的例子做一个点击计数的按钮。import React, { useState } from react; function Counter() { const [count, setCount] useState(0); // 解构出状态和修改函数 return ( div p你点了 {count} 次/p button onClick{() setCount(count 1)}点我1/button /div ); }useState(0)里面的0就是初始值。它返回一个数组第一个是当前值第二个是用来修改这个值的函数。我用解构直接取名count和setCount这样好记。2. 我发现的一个坑状态更新可能是“过期的”一开始我直接在setCount(count 1)里面写有时候连续点两次它居然只加了一次。后来查资料才知道如果新状态依赖旧状态最好用函数式更新// 更保险的写法 button onClick{() setCount(prevCount prevCount 1)}点我1/button这样 React 会确保用的是最新的那个旧值。初学的时候可以先记住只要新值依赖旧值就用函数写法。3. 如果状态是对象或数组怎么办我一开始直接setUser({ name: 新名字 })结果原来的age就没了。后来明白必须把原来的值也复制过来。const [user, setUser] useState({ name: 小明, age: 18 }); // 正确用 ... 展开旧对象再覆盖要改的字段 setUser(prev ({ ...prev, name: 小红 }));数组也一样不要用push要用[...prev, 新元素]。二、useEffect —— 处理“副作用”我刚学的时候老问什么是副作用老师说只要不是直接渲染 UI 的事情比如发请求、改标题、设置定时器、操作 DOM都叫副作用。useEffect就是专门做这些的。1. 基础写法useEffect(() { // 这里写副作用代码 return () { // 清理工作可选 }; }, [依赖项]);2. 三种情况我开始总记混我给自己画了个表依赖项执行时机我常用在不写每次渲染完都执行几乎不用容易出问题[]空数组只有组件第一次出现时执行一次页面一打开就请求数据设置订阅[count]有依赖第一次 依赖变化时执行比如 count 变了更新网页标题例子1组件加载时请求数据useEffect(() { fetch(https://api.example.com/user) .then(res res.json()) .then(data setUser(data)); }, []); // 空数组 → 只请求一次例子2标题跟着 count 变useEffect(() { document.title 你有 ${count} 条消息; }, [count]); // count 变了就改标题3. 清理副作用 —— 我吃过亏有一次我写了个定时器切换页面后它还在跑控制台一直输出还把内存吃满了。原来是因为我没有清理。正确的写法是在useEffect里return一个清理函数。useEffect(() { const timer setInterval(() { console.log(滴答); }, 1000); return () { clearInterval(timer); // 组件卸载时清除 }; }, []);4. 小心无限循环我犯过我有一次傻傻地写了useEffect(() { setCount(count 1); }, [count]); // 依赖 count里面又改 count → 死循环页面直接卡死。记住不要在 useEffect 里更新依赖数组里的状态除非你真的知道自己在做什么。三、useContext —— 解决“props 一层层传递”的麻烦1. 为什么需要它比如我有个主题亮色/暗色最外层的 App 组件想传给最里面的按钮如果用 props 就得层层传递中间那些组件其实根本不用这个属性太烦了。useContext可以让里面的组件直接“跳过”中间层拿到数据。2. 三步走我总结了三个步骤创建一个 Contextconst ThemeContext createContext()用Provider包住父组件并传入要共享的值子组件里用useContext(ThemeContext)接收代码示例import React, { createContext, useContext, useState } from react; const ThemeContext createContext(); // 1. 创建 function App() { const [theme, setTheme] useState(light); // 2. 提供值 return ( ThemeContext.Provider value{{ theme, setTheme }} Toolbar / /ThemeContext.Provider ); } function Toolbar() { return ThemedButton /; } function ThemedButton() { // 3. 使用 const { theme, setTheme } useContext(ThemeContext); return ( button style{{ background: theme dark ? #333 : #eee }} onClick{() setTheme(theme dark ? light : dark)} 当前主题{theme} /button ); }我一开始老忘记用Provider包裹结果子组件拿到的是undefined。后来就记住了创建 → Provider 包住 → useContext 拿值。3. 一个小提醒如果一个 Context 里的值经常变比如用户打字的内容那所有用了这个 Context 的组件都会重新渲染。如果性能变差可以考虑把不变的部分和变化的部分拆成不同的 Context。初学者可以先不管遇到卡顿了再优化。四、综合练手做一个带主题的待办清单为了把三个 Hook 都用上我写了一个小项目待办清单还能切换亮色/暗色主题并且主题会保存在浏览器里刷新后还在。import React, { useState, useEffect, useContext, createContext } from react; // 主题 Context const ThemeContext createContext(); // 主题提供者负责保存主题到 localStorage function ThemeProvider({ children }) { const [theme, setTheme] useState(() { return localStorage.getItem(theme) || light; }); useEffect(() { localStorage.setItem(theme, theme); }, [theme]); return ( ThemeContext.Provider value{{ theme, setTheme }} {children} /ThemeContext.Provider ); } // 待办主界面 function TodoApp() { const [todos, setTodos] useState([]); const [input, setInput] useState(); const addTodo () { if (input.trim()) { setTodos([...todos, { id: Date.now(), text: input, done: false }]); setInput(); } }; const toggleTodo (id) { setTodos(todos.map(todo todo.id id ? { ...todo, done: !todo.done } : todo )); }; return ( div h2我的待办/h2 div input value{input} onChange{(e) setInput(e.target.value)} placeholder写点什么... / button onClick{addTodo}添加/button /div ul {todos.map(todo ( li key{todo.id} onClick{() toggleTodo(todo.id)} style{{ textDecoration: todo.done ? line-through : none }} {todo.text} /li ))} /ul /div ); } // 主题切换按钮 function ThemeSwitcher() { const { theme, setTheme } useContext(ThemeContext); return ( button onClick{() setTheme(theme light ? dark : light)} 切换到 {theme light ? 暗色 : 亮色} 主题 /button ); } // 顶层组件 function App() { return ( ThemeProvider ThemeSwitcher / TodoApp / /ThemeProvider ); } export default App;这里面useState管待办列表、输入框内容和主题状态。useEffect在主题变化时自动存到localStorage。useContext让ThemeSwitcher不用通过 props 就能修改主题。五、两个一定要记住的规则我一开始不知道这两条老报错。只在最顶层调用 Hook不能在if、循环、函数嵌套里面调用useState等。要放在函数组件的最外面。// ❌ 错误 if (something) { const [x, setX] useState(0); } // ✅ 正确 const [x, setX] useState(0);只在 React 函数组件或自定义 Hook 里调用不要在普通 JS 函数里调用。建议安装eslint-plugin-react-hooks插件它会自动帮你检查。六、我的小总结Hook作用我记的关键点useState让函数组件有自己的状态函数式更新、不可变数据用展开运算、初始值复杂时用函数useEffect做“额外的事”比如请求、定时器依赖数组要写对记得清理小心无限循环useContext跨组件共享数据省去 props 传递三步createContext → Provider → useContext学完这三个我已经能写很多有意思的小应用了。当然 React 还有useReducer、useMemo、useCallback等更高级的但先把这三个用熟后面就会轻松很多。希望这篇笔记能帮到和我一样的初学者。如果有不对的地方欢迎大家指正一起交流

相关新闻