玩转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

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

​

相关推荐
excel13 分钟前
Nuxt 中使用 useHead 优化 SEO 与 GEO
前端
IT_陈寒1 小时前
SpringBoot启动慢得像蜗牛?原来是这个配置在捣鬼
前端·人工智能·后端
计算机魔术师1 小时前
Muse Spark跑赢Gemini,但真正的底牌是这种设计
前端
PYB32 小时前
【Web·JS·基础】函数的使用和展运算符...objs
前端·javascript
指针向南2 小时前
Chrome读不了HEIC怎么办:原生解码和WASM两条路
前端·图像处理·人工智能·chrome·计算机视觉·wasm
默_笙2 小时前
🚥 给 RAG 装一台心电监护仪:LangSmith 全链路观测(上)
前端·javascript
前端snow3 小时前
ai agent --- mem0 外挂记忆系统
前端
变与不变8064 小时前
js同步和异步难点重点详解
开发语言·javascript·ecmascript
特立独行的猫a4 小时前
用仓颉写一个 Tauri:IPC 的每次往返实现原理(web层到仓颉层的触发过程)
前端·ui·harmonyos·tauri·鸿蒙·仓颉
Dovis(誓平步青云)4 小时前
多个链接不等于多份证据,新闻核验看板怎样合并来源
java·服务器·前端·javascript·人工智能·pdf·电脑