Vue 人转 React 必踩的 useEffect 坑,我来填平了

这篇文章是写给正从 Vue 转向 React、被 useEffect 弄懵的你。

前言

如果你刚从 Vue 进入 React 世界,useEffect 可能是你最容易踩坑的 Hook。它看起来像 Vue 的生命周期函数,实际却大不相同。

你是不是也遇到过这些疑惑?

  • “它到底是 mounted 还是 updated 的替代?”
  • “为什么我的副作用逻辑会无限触发?”
  • “依赖项到底该不该加?加了它就炸,不加它报错?”

别急,这篇文章我会从 运行时机、依赖项、清除机制常见陷阱,逐一帮你搞清楚 useEffect 的使用姿势。


什么是 useEffect

在 React 中,组件的渲染是纯函数,意味着你不能在函数里直接写副作用操作(比如请求接口、DOM 操作、设置定时器等)。而 useEffect,就是用来处理这些副作用(Side Effects) 的 Hook。

React 官网对它的定义是:

“在函数组件中处理副作用的方式”

通俗点说,它的作用类似 Vue 的 mountedwatchwatchEffect 加在一起,但规则更明确,也更容易踩坑。


useEffect 的基础用法

举个栗子

import { useEffect, useState } from "react";

export default function Example() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    console.log("count 改变了:", count);
  }, [count]);

  return <button onClick={() => setCount(count + 1)}>点击:{count}</button>;
}

在这里插入图片描述

上面栗子中:

  • useEffect 在组件 首次挂载count 变化时 执行。
  • [count] 是依赖项数组,只有它发生变化时才会重新执行副作用逻辑。

那么为什么一开始我们说:

useEffect的作用类似 Vue 的 mountedwatchwatchEffect 加在一起”

我们先简单介绍下 useEffect 接收的参数:

  1. setup 副作用函数,用于执行副作用,它返回一个 cleanup 清理函数。
  2. 一个 依赖项列表,包括这些函数使用的每个组件内的值。

模拟 Vue

useEffect 实现 mounted 效果

export default function Example() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    console.log("count 改变了:", count);
  }, []);

  return <button onClick={() => setCount(count + 1)}>点击:{count}</button>;
}

依赖项列表 改为传入空数组 [],就能实现 Vue 中的 mounted 效果:

在这里插入图片描述

useEffect 实现 watch 效果

export default function Example() {
  const [count, setCount] = useState(0);
  const [num, setNum] = useState(0);

  useEffect(() => {
    console.log("count 改变了:", count);
  }, [count]);

  return (
    <>
      <button onClick={() => setCount(count + 1)}>点击count:{count}</button>
      <button onClick={() => setNum(num + 1)}>点击num:{num}</button>
    </>
  );
}

我们现在传入 [count],表示用于监听 count 的响应式变化,再创建一个 num 去作对比,我们看看效果:

在这里插入图片描述

传入 [count],Effect 会像 Vue 中的 watch 一样去监听 count 的数据变化,不同点在于旧值 oldVal 的值只能从 cleanup 函数中获取。

export default function Example() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    console.log("count新值:", count);
    return () => {
      console.log("count旧值:", count);
    };
  }, [count]);

  return (
    <>
      <button onClick={() => setCount(count + 1)}>点击count:{count}</button>
    </>
  );
}

在这里插入图片描述

useEffect 的执行顺序:

  1. 组件挂载到页面时,执行 setup 函数。
  2. 重新渲染依赖项后,使用旧的 propsstate 运行 cleanup 函数
  3. 重新渲染依赖项后,使用新的 propsstate 运行 setup 函数
  4. 组件从页面卸载后,cleanup 函数运行最后一次。

不同依赖项的行为差异

写法行为
useEffect(() => {})每次 render 都执行
useEffect(() => {}, [])仅初次执行(类似 Vue 的 mounted)
useEffect(() => {}, [a, b])依赖 a/b 变化才执行

几个常见的 useEffect 使用场景

场景一:接口请求

useEffect(() => {
  async function fetchData() {
    const res = await fetch('/api/user');
    const data = await res.json();
    setUser(data);
  }

  fetchData();
}, []);

这里有个需要注意的点:使用 async 时需要创建一个新的函数去使用,不能直接在 setup 函数使用,例如:

useEffect(async () => {
    const res = await fetch('/api/user');
    const data = await res.json();
    setUser(data);
}, []);

这样会直接报错,因为 useEffect 返回的是一个 cleanup 函数,而 async 返回的是一个 Promise 函数。

场景二:事件监听(添加/移除)

useEffect(() => {
  const handleResize = () => {
    setWindowWidth(window.innerWidth);
  };

  window.addEventListener('resize', handleResize);

  return () => {
    window.removeEventListener('resize', handleResize);
  };
}, []);

注意使用 cleanup 函数清除监听,避免内存泄漏。

场景三:长链接(WebSocket等)

useEffect(() => {
  const socket = new WebSocket('ws://example.com');
  socket.onmessage = (e) => setMessage(e.data);

  return () => socket.close(); // 卸载时断开连接
}, []);

同上,组件销毁时需要使用 cleanup 函数关闭 socket,避免内存泄漏。

陷阱1:useEffect 无限触发

export default function Example() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    setCount(count + 1);
  }, [count]);

  return (
    <>
      <button onClick={() => setCount(count + 1)}>点击count:{count}</button>
    </>
  );
}

你一改 count,它就触发 effect,effect 里又改 count,陷入死循环。

依赖项中包含的变量 不要在副作用里更新它

在这里插入图片描述

陷阱2:忘记清除副作用

useEffect(() => {
  const socket = new WebSocket('ws://example.com');
  socket.onmessage = (e) => setMessage(e.data);
}, []);

这段代码每次调用 useEffect 的时候就会创建一个 socket 链接,如果我们没有在 cleanup 函数中计算关闭链接,那么每次触发 useEffect 都会建立一条新的链接,导致性能问题甚至报错。

优化:

useEffect(() => {
  const socket = new WebSocket('ws://example.com');
  socket.onmessage = (e) => setMessage(e.data);

  return () => socket.close(); // 卸载时断开连接
}, []);

总结

useEffect 是 React 中处理副作用的核心 Hook,理解它的运行机制能让你写出更健壮的组件逻辑。

  • 它在组件渲染后执行,依赖项更新时重新触发。
  • 可以返回清除函数,清理事件、定时器、长链接等副作用。
  • 编写 useEffect 时,要关注依赖、时机和清除机制
  • 警惕副作用中修改依赖项,容易陷入无限循环。

希望这篇文章能帮你快速掌握 useEffect,如果你觉得有帮助,别忘了点个赞👍或关注我后续的 重学 React 系列!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值