🎣 React Hooks:从“状态焦虑”到“副作用自由”的奇幻漂流

72 阅读4分钟

别再被 useStateuseEffect 整懵了!本文带你用最轻松的方式,搞懂 React Hooks 的灵魂所在。


🌟 你是否也经历过这些“Hook 焦虑症”?

  • 每次写 useState 都在纠结:到底传值还是传函数?
  • useEffect 的依赖数组像一道玄学题,加不加?怎么加?
  • 组件一更新,定时器满天飞,内存泄漏警告响个不停……
  • 更可怕的是:明明写了 [],怎么还执行了两次?!

别慌!今天我们就来一场 React Hooks 的脱口秀式科普,让你笑着学会,笑着写出干净、健壮、优雅的函数组件!


🧠 一、useState:不只是“变量”,而是“会呼吸的状态”

❓ 初始值是简单数字?直接传!

const [count, setCount] = useState(0);

这谁都会。但如果你的初始值需要“算一下”呢?

💡 复杂计算?用初始化函数

const [num, setNum] = useState(() => {
  const a = expensiveCalculation(); // 假设很耗时
  return a * 2;
});

关键点:这个函数只在组件首次渲染时执行一次,后续更新完全忽略它。
不要在这里写异步useState 要的是确定性——状态不能“等两秒才知道是多少”。

🤔 那我怎么在组件加载时请求数据?
👉 别急,useEffect 马上登场!


🌀 二、useEffect:React 的“副作用管家”

🧼 什么是“副作用”?

在纯函数的世界里:

  • 输入 → 输出(比如 (x, y) => x + y
  • 没有网络请求、没有定时器、没有 DOM 操作

而现实世界偏偏充满“副作用”:

“我要发请求!”、“我要监听窗口大小!”、“我要启动一个每秒滴答的钟!”

React 说:把这些脏活交给 useEffect 吧!


🛠️ useEffect 的三种经典姿势

1️⃣ 挂载即执行(onMounted)

useEffect(() => {
  console.log('组件出生啦!');
}, []); // 空数组 = 只跑一次

⚠️ 注意:React 18 严格模式下,开发环境会故意执行两次,帮你提前发现副作用问题!别慌,生产环境只跑一次。

2️⃣ 依赖变化就执行(watcher)

useEffect(() => {
  document.title = `你点了 ${count} 次`;
}, [count]); // count 变了才跑

🔍 依赖项必须包含 effect 内用到的所有外部变量!否则你会读到“过期的闭包值”。

3️⃣ 清理副作用(onUnmounted / before update)

useEffect(() => {
  const timer = setInterval(() => console.log('滴答'), 1000);
  return () => clearInterval(timer); // 清理!
}, []);

💥 不清理 = 内存泄漏!多个定时器同时跑,页面卡成 PPT,用户想砸电脑……


🎭 实战案例:点击 +1,偶数时显示子组件

我们来看一段“教科书级”的代码:

// App.jsx
useEffect(() => {
  const timer = setInterval(() => console.log(num), 1000);
  return () => clearInterval(timer);
}, [num]); // 依赖 num

每次 num 变化,旧定时器被清除,新定时器启动。
✅ 安全!✅ 干净!✅ 不泄漏!

而子组件 Demo

// Demo.jsx
useEffect(() => {
  const timer = setInterval(() => console.log('timer'), 1000);
  return () => clearInterval(timer);
}, []); // 只在挂载/卸载时处理

num 变奇数,<Demo /> 被卸载,它的清理函数自动执行!
👉 React 自动帮你管理生命周期,无需手动调用 onUnmounted


📈 图解 React 核心模型:Event → State → View

React 的核心思想可以用一张图概括:

e9f97fc406e8403ab0c9461556db5410.png

📌 事件驱动模型

  • 用户触发 Event(如点击按钮)
  • 触发状态变更(setNum()
  • 状态改变后,View 自动重渲染
  • 新 View 再响应新的 Event,形成闭环

✅ 这就是 React 的“单向数据流”哲学:一切皆由状态驱动,视图只是状态的映射。


🤯 常见误区 & 幽默小剧场

❌ 误区1:“我在 useState 里发请求!”

const [data] = useState(async () => {
  const res = await fetch('/api');
  return res.json(); // ❌ 报错!useState 不接受 Promise!
});

🗣️ React 内心 OS:
“兄弟,我是同步的!你要异步?找 useEffect 啊!”

✅ 正确姿势:

useEffect(() => {
  fetch('/api').then(res => setData(res.json()));
}, []);

❌ 误区2:“依赖数组空着最安全!”

useEffect(() => {
  console.log(user.name); // user 是 props
}, []); // ❌ 如果 user 变了,这里还是旧值!

🐞 这叫 “陈旧闭包陷阱” —— 你看到的 user 是组件第一次渲染时的快照!

✅ 解法:把 user 加进依赖,或用 useCallback / useRef 缓解。


🧩 三、自定义 Hook:把逻辑抽离成“乐高积木”

React Hooks 最酷的地方?你可以自己造 Hook!

比如封装一个通用的数据请求:

function useFetch(url) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);

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

  return { data, loading };
}

// 使用
function UserProfile() {
  const { data, loading } = useFetch('/api/user');
  if (loading) return <div>加载中...</div>;
  return <div>你好,{data.name}!</div>;
}

🧱 逻辑复用从未如此简单!告别 HOC,告别 render props,拥抱函数式组合!


🎉 结语:Hooks 不是魔法,而是“思维升级”

React Hooks 的本质,不是语法糖,而是一种思维方式的转变

  • 状态 → 用 useState 管理
  • 副作用 → 用 useEffect 隔离
  • 逻辑复用 → 用自定义 Hook 抽离

它们让函数组件拥有了类组件的能力,却更轻、更灵活、更函数式。

🌈 记住:
状态要确定,副作用要清理,依赖要诚实。
做到这三点,你就是 Hooks 高手!