玩转React Hooks

作为前端摸爬滚打近六年的开发者,我至今记得第一次用useState时那种惊艳感------代码量直接砍半!但很快就被莫名其妙的闭包陷阱和无限循环教做人。今天就用真实踩坑经历,聊聊Hooks那些看似简单却暗藏玄机的细节。


🔥 闭包陷阱:Stale Closure

刚用useEffect时我写过这样的代码:

javascript 复制代码
function Counter() {
  const [count, setCount] = useState(0);
  
  useEffect(() => {
    const timer = setInterval(() => {
      console.log(count); // 永远输出0!
      setCount(count + 1);
    }, 1000);
    return () => clearInterval(timer);
  }, []);
  
  return <div>{count}</div>;
}

发现计时器数字永远停在1后我才明白:useEffect的依赖数组为空,导致回调函数捕获的是初始count值。解决方案有两种:

方案A:用函数更新保证最新值

javascript

ini 复制代码
setCount(prevCount => prevCount + 1);

方案B:正确声明依赖

javascript 复制代码
useEffect(() => {
  const timer = setInterval(() => {
    setCount(count + 1);
  }, 1000);
  return () => clearInterval(timer);
}, [count]); // 依赖项补齐

⚡ 性能优化:避免重复计算

某次我封装了个数据筛选函数:

javascript 复制代码
function ProductList({ products }) {
  const [filter, setFilter] = useState('');
  
  const filteredProducts = products.filter(product => 
    product.name.includes(filter)
  );
  
  // 每次渲染都会重新计算filteredProducts!
}

当产品列表很大时,页面卡顿明显。这时就该useMemo出场了:

javascript 复制代码
const filteredProducts = useMemo(() => {
  return products.filter(product => 
    product.name.includes(filter)
  );
}, [products, filter]); // 仅依赖变化时重新计算

🎯 事件监听:及时清理资源

曾在组件里直接绑定事件:

javascript 复制代码
function ResizeHandler() {
  const handleResize = () => {
    console.log(window.innerWidth);
  };
  
  window.addEventListener('resize', handleResize);
  // 忘记移除监听!内存泄漏警告!
}

后来改成useEffect+清理机制才解决:

javascript 复制代码
useEffect(() => {
  const handleResize = () => {
    console.log(window.innerWidth);
  };
  
  window.addEventListener('resize', handleResize);
  return () => window.removeEventListener('resize', handleResize);
}, []);

💡 自定义Hook:逻辑复用的艺术

把业务逻辑抽成自定义Hook后,代码清爽多了:

javascript 复制代码
function useApi(url) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    fetch(url)
      .then(res => res.json())
      .then(data => {
        setData(data);
        setLoading(false);
      });
  }, [url]);

  return { data, loading };
}

// 在组件中直接使用
function UserPanel() {
  const { data, loading } = useApi('/api/userinfo');
  return loading ? <Spinner /> : <Profile data={data} />;
}

🚀 实战建议

  1. 依赖数组要诚实
    不要为了消除警告随便写eslint-disable-next-line,依赖项缺失往往是bug的根源
  2. 拆分复杂组件
    超过200行的Hook组件建议拆分成多个自定义Hook,比如useUserData + useUserActions
  3. 使用useCallback缓存函数
    当函数作为props传递时,用useCallback避免子组件不必要的重渲染

⭐ 写在最后

请大家不吝赐教,在下方评论或者私信我,十分感谢🙏🙏🙏.

✅ 认为我某个部分的设计过于繁琐,有更加简单或者更高逼格的封装方式

✅ 认为我部分代码过于老旧,可以提供新的API或最新语法

✅ 对于文章中部分内容不理解

✅ 解答我文章中一些疑问

✅ 认为某些交互,功能需要优化,发现BUG

✅ 想要添加新功能,对于整体的设计,外观有更好的建议

✅ 一起探讨技术加qq交流群:906392632

最后感谢各位的耐心观看,既然都到这了,点个 👍赞再走吧!

相关推荐
Revol_C10 分钟前
【Git 操作笔记】第1期--云代码仓库更换服务商,本地如何批量更新对应项目的git地址(持续更新...)
前端·git
Miracle_G19 分钟前
每日一个知识点:实现AJAX和Fetch请求进度条
前端·javascript
数字人直播19 分钟前
视频号数字人直播带货,青否数字人提供全套解决方案!
前端·javascript·后端
Juchecar38 分钟前
Vue3 模块组织及 Import 机制详解 - 初学者完全指南
前端·vue.js
KenXu40 分钟前
2025 Figma to Code MCP 深度横评
前端
前端进阶者44 分钟前
electron-vite_19配置环境变量
前端·electron·vite
yangholmes888844 分钟前
如何在 web 应用中使用 GDAL (三)
前端·webassembly
WebGirl1 小时前
实现将html页面导出word (.docx)
前端
眼镜会飞1 小时前
Flutter 3.x新版android端的build.gradle.kts文件配置arm64-v8a和armeabi-v7a等
android·前端·flutter