【React-3/Lesson76(2025-12-18)】React Hooks 与函数式组件开发详解🧠

16 阅读6分钟

🧠在现代前端开发中,React 已经全面拥抱函数式编程范式。通过 Hooks,开发者可以在不编写 class 的情况下使用状态(state)和生命周期等特性。本文将深入解析你所接触的代码片段,并系统性地补充相关知识,涵盖 useStateuseEffect、纯函数、副作用、组件挂载/更新/卸载机制、响应式状态管理等核心概念。


🔁 useState:让函数组件拥有状态

在传统 React 中,只有类组件才能拥有状态(state)。而 useState Hook 的出现,彻底改变了这一限制。

const [num, setNum] = useState(0);

这行代码做了三件事:

  1. 声明一个名为 num 的状态变量;
  2. 提供一个名为 setNum 的函数用于更新该状态;
  3. 初始值为 0

✨ 初始化支持函数形式(惰性初始化)

当初始状态需要通过复杂计算获得时,可以传入一个初始化函数

const [num, setNum] = useState(() => {
  const num1 = 1 + 2;
  const num2 = 2 + 3;
  return num1 + num2; // 返回 6
});

⚠️ 注意:这个函数必须是同步的纯函数,不能包含异步操作(如 fetch),因为 React 需要确保状态的确定性和可预测性。

🔄 状态更新函数支持回调形式

更新状态时,可以传入一个函数,其参数是上一次的状态值:

setNum((prevNum) => {
  console.log(prevNum); // 打印旧值
  return prevNum + 1;   // 返回新值
});

这种方式在批量更新异步环境中特别安全,避免因闭包捕获旧状态而导致错误。


⚙️ useEffect:处理副作用的瑞士军刀

useEffect 是 React 中处理副作用(side effects)的核心 Hook。所谓“副作用”,是指那些不在纯函数范畴内的操作,例如:

  • 数据获取(如 API 请求)
  • 手动 DOM 操作
  • 订阅事件(如 WebSocket)
  • 启动定时器(setInterval / setTimeout

📌 基本用法

useEffect(() => {
  console.log('effect');
}, [num]);
  • 第一个参数:副作用函数(在渲染后执行)
  • 第二个参数:依赖数组(dependency array)

🔍 依赖项的三种情况

依赖项行为类比 Vue 生命周期
[](空数组)仅在组件挂载后执行一次onMounted
[a, b]ab 变化时重新执行watch([a, b])
无依赖项(省略第二个参数)每次渲染后都执行onMounted + onUpdated

💡 在 React 18 的 <StrictMode> 下,开发环境会故意双次调用 useEffect(不含依赖或依赖为空时),以帮助开发者发现潜在的副作用问题(如未正确清理资源)。

🧹 清理副作用:返回清理函数

许多副作用需要在组件更新前或卸载时清理,否则会导致内存泄漏或重复订阅。

useEffect(() => {
  const timer = setInterval(() => {
    console.log(num);
  }, 1000);

  return () => {
    console.log('remove');
    clearInterval(timer); // 清理定时器
  };
}, [num]);
  • 返回的函数会在下一次 effect 执行前调用,或在组件卸载时调用。
  • 这利用了闭包机制:清理函数能访问到创建它时的 timer 变量。

✅ 最佳实践:所有开启的资源(定时器、订阅、监听器)都必须有对应的清理逻辑。


🧼 纯函数 vs 副作用

理解 useEffect 的设计哲学,必须先理解**纯函数(Pure Function)**的概念。

✅ 纯函数的特点

  • 相同输入 → 相同输出
  • 无副作用:不修改外部状态、不发起网络请求、不操作 DOM
  • 无随机性(如 Math.random()
// ✅ 纯函数
const add = (x, y) => x + y;

❌ 非纯函数(有副作用)

// ❌ 修改传入的数组(改变外部状态)
function add(nums) {
  nums.push(3); // 副作用!
  return nums.reduce((pre, cur) => pre + cur, 0);
}

React 组件本身应尽可能接近纯函数:props → JSX。但现实应用离不开副作用,因此 useEffect 被设计为隔离副作用的沙盒


🧩 组件生命周期在函数式组件中的映射

Class 组件生命周期函数式组件(Hooks)
componentDidMountuseEffect(() => {}, [])
componentDidUpdateuseEffect(() => {}, [dep])
componentWillUnmountuseEffect(() => { return () => {} }, [])

🔄 注意:useEffect 合并了挂载、更新、卸载三个阶段,通过依赖项和返回函数实现精细控制。


🏗️ 项目结构与入口分析

📄 main.jsx:应用入口

createRoot(document.getElementById('root')).render(<App />);
  • 使用 React 18 的 createRoot API(并发模式)
  • 渲染 <App />#root 容器
  • 注释掉的 <StrictMode> 是开发辅助工具,用于暴露潜在问题(如重复 effect)

🎨 样式文件 index.cssApp.css

  • 使用 CSS 自定义属性(:root)实现主题切换(亮色/暗色)
  • 响应式设计(min-width: 320px
  • 悬停动画、焦点样式等增强用户体验

🔍 深入 Demo.jsx:副作用与清理

export default function Demo() {
  useEffect(() => {
    console.log('123123'); // 模拟 onMounted
    const timer = setInterval(() => {
      console.log('timer');
    }, 1000);

    return () => {
      console.log('remove');
      clearInterval(timer);
    };
  }, []); // 仅挂载时执行

  return <div>偶数Demo</div>;
}
  • 即使 Demo 组件被多次渲染(因父组件 App 更新),由于依赖项为空,定时器只创建一次
  • App 卸载 Demo(如条件渲染切换),清理函数会执行,防止内存泄漏

📊 状态驱动 UI:响应式核心

App.jsx 中:

{num % 2 == 0 ? '偶数' : '奇数'}

这体现了 React 的核心思想:UI 是状态的函数
每当 num 变化,React 会重新执行组件函数,生成新的 JSX,然后高效地更新 DOM。


🚫 为什么不能在 useState 中直接异步初始化?

// ❌ 错误!useState 不支持异步初始化
const [data, setData] = useState(async () => {
  const res = await fetch(...);
  return res.json();
});

原因:

  • React 需要同步确定初始状态,以便进行协调(reconciliation)
  • 异步操作结果不确定,破坏纯函数原则

✅ 正确做法:在 useEffect 中请求数据

useEffect(() => {
  queryData().then(data => setNum(data));
}, []);

其中 queryData 是一个模拟异步请求的函数(见 App.jsx):

async function queryData() {
  const data = await new Promise((resolve) => {
    setTimeout(() => resolve(666), 2000);
  });
  return data;
}

🧪 开发者工具与调试技巧

  • 利用 console.log 观察 effect 执行时机
  • 注意 Strict Mode 下的双次调用(仅开发环境)
  • 使用 React DevTools 检查组件状态和依赖

✅ 总结:React Hooks 最佳实践

  1. 状态管理:用 useState 声明响应式状态,更新时优先使用回调形式
  2. 副作用隔离:所有非纯操作放入 useEffect
  3. 依赖声明:精确列出 effect 所依赖的所有变量(ESLint 插件可自动检测)
  4. 资源清理:务必在 effect 中返回清理函数
  5. 避免异步初始化:数据请求放在 useEffect
  6. 理解闭包:effect 和清理函数通过闭包捕获变量,注意 stale closure 问题(可通过 ref 解决)

通过以上详尽解析,你应该已经掌握了 React Hooks 的核心机制与工程实践。记住:函数式组件 + Hooks = 现代 React 开发的黄金标准。继续深入,你将能构建出高性能、可维护、可预测的前端应用!🚀