React Hooks 编程模式与组件复用最佳实践

React Hooks 编程模式与组件复用最佳实践

Ethan
2025-08-02 发布 / 正在检测是否收录...

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 的精髓。

© 版权声明
THE END
喜欢就支持一下吧
点赞 1 分享 收藏

评论 (0)

取消

Warning: file_put_contents(/var/www/html/usr/cache/pagecache/48/48d3069bc05831f57de3bd0949054c10.cache): failed to open stream: No such file or directory in /var/www/html/usr/plugins/PageCache/Plugin.php on line 188