React Hooks 自 2019 年发布以来,已经成为 React 开发的标准范式。从 Class 组件到函数组件的转变,不仅仅是 API 的变化,更是编程思维的重塑。本文将深入探讨 Hooks 的编程模式和组件复用策略。
从 Class 到 Hooks:思维转变
// Class 组件:生命周期驱动\nclass Timer extends React.Component {\n state = { count: 0 };\n componentDidMount() { this.timer = setInterval(() => this.setState(s => ({ count: s.count + 1 })), 1000); }\n componentWillUnmount() { clearInterval(this.timer); }\n render() { return <div>{this.state.count}</div>; }\n}\n\n// Hooks:逻辑驱动\nfunction Timer() {\n const [count, setCount] = useState(0);\n useEffect(() => {\n const timer = setInterval(() => setCount(c => c + 1), 1000);\n return () => clearInterval(timer); // 清理函数 = componentWillUnmount\n }, []); // 空依赖 = componentDidMount\n return <div>{count}</div>;\n}闭包陷阱与 useRef
// 陷阱:handleClick 捕获了初始的 count 值\nfunction Counter() {\n const [count, setCount] = useState(0);\n function handleClick() {\n setTimeout(() => { console.log(count); }, 3000);\n // 3秒后打印的是点击时的 count,而不是 3秒后的 count\n }\n}\n\n// 解决方案1:使用函数式更新\nfunction handleClick() {\n setTimeout(() => { setCount(c => { console.log(c); return c; }); }, 3000);\n}\n\n// 解决方案2:使用 useRef 保存最新值\nfunction Counter() {\n const [count, setCount] = useState(0);\n const countRef = useRef(count);\n countRef.current = count; // 始终保持最新\n function handleClick() {\n setTimeout(() => { console.log(countRef.current); }, 3000);\n }\n}useEffect 的完整心智模型
useEffect 不是在某个生命周期执行的操作,而是"在渲染之后,根据依赖变化同步副作用"。React 18 的 Strict Mode 会在开发环境下双重调用 useEffect,这是为了帮你提前发现缺少清理函数的问题——如果你的 useEffect 在 Double Invocation 下行为异常,说明你的副作用没有正确清理。
useEffect(() => {\n const controller = new AbortController();\n fetchData(controller.signal);\n return () => controller.abort(); // 清理:取消未完成的请求\n}, [query]);useMemo 和 useCallback:何时用?
黄金法则:不要过早优化。只有当性能问题真正出现时(通过 React DevTools Profiler 确认),才使用 useMemo 和 useCallback。它们不是免费的——每次渲染都要比较依赖数组,这本身也有开销。
// useMemo:缓存计算结果\nconst sortedList = useMemo(() => {\n return list.sort((a, b) => a.name.localeCompare(b.name));\n}, [list]);\n\n// useCallback:缓存函数引用(配合 React.memo 使用)\nconst handleClick = useCallback((id) => {\n setSelectedId(id);\n}, []);\nconst MemoChild = React.memo(Child);\n// 只有 handleClick 引用不变,MemoChild 才不会重渲染自定义 Hook 的设计模式
1. 数据获取 Hook
function useData(fetcher: () => Promise, deps: any[]) {\n const [data, setData] = useState(null);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState(null);\n\n useEffect(() => {\n let cancelled = false;\n setLoading(true);\n fetcher()\n .then(d => { if (!cancelled) setData(d); })\n .catch(e => { if (!cancelled) setError(e); })\n .finally(() => { if (!cancelled) setLoading(false); });\n return () => { cancelled = true; };\n }, deps);\n\n return { data, loading, error };\n} 2. 防抖 Hook
function useDebounce(value: T, delay: number): T {\n const [debouncedValue, setDebouncedValue] = useState(value);\n useEffect(() => {\n const timer = setTimeout(() => setDebouncedValue(value), delay);\n return () => clearTimeout(timer);\n }, [value, delay]);\n return debouncedValue;\n} 总结
React Hooks 的核心理念是"从生命周期思维转向数据流思维"。不要问"组件挂载时我要做什么",而要问"这个副作用依赖于哪些数据"。当你能自然地用数据依赖来思考副作用时,你就真正掌握了 Hooks 的精髓。
评论 (0)