"懒人福音:使用 React Hooks (useState & useEffect) 让你的代码自动干活!"

148 阅读5分钟

在现代 React 开发中,函数组件 + Hooks 已经成为主流开发模式。Hooks 提供了类组件所拥有的状态管理、生命周期控制和副作用处理能力,同时保持了代码的简洁性和可维护性。

本文将以一个完整的示例项目为基础,深入讲解 useStateuseEffect 的使用方式,特别是重点解析 useEffect 的副作用机制、生命周期模拟以及资源清理逻辑,并通过两个实际组件(App.jsTimer.js)来展示 Hooks 在真实场景下的应用。


🧠 函数是一等公民(First-Class Functions)

JavaScript 中的函数是“一等对象”,这意味着:

  • 可以赋值给变量
  • 可以作为参数传递给其他函数
  • 可以作为函数的返回值
  • 可以拥有属性和方法(因为函数本质是对象)

这一特性为函数式编程提供了强大支持。React 的函数组件本质上就是一个返回 JSX 的函数,而 Hooks 正是在这样的函数中使用的特殊函数,它们以 use 开头,比如 useState, useEffect, useRef 等。


🧪 useState:状态管理的基础

useState 是我们在函数组件中引入本地状态的 Hook。

示例代码片段(来自 App.js):

const [count, setCount] = useState(0);
const [num, setNumber] = useState(0);
const [repos, setRepos] = useState([]);
const [isTimerOn, setIsTimerOn] = useState(true);

每个调用 useState() 都会创建一个独立的状态单元,并提供更新函数。这样有助于我们保持逻辑清晰、职责单一。

✅ 使用多个 useState 而不是将所有状态合并成一个对象,是推荐的最佳实践。

详细解释:

  • countsetCount:用于计数器1的状态管理。
  • numsetNumber:用于计数器2的状态管理。
  • repossetRepos:用于存储从 GitHub API 获取的仓库数据。
  • isTimerOnsetIsTimerOn:用于控制 <Timer /> 组件是否显示。

💥 useEffect:副作用处理的核心

如果说 useState 是函数组件的状态之源,那么 useEffect 就是其行为之本。它是 React 中用于处理副作用的核心 Hook。

基本语法结构

useEffect(() => {
  // 副作用逻辑

  return () => {
    // 清理逻辑(可选)
  };
}, [依赖项]);
  • 第一个参数是一个回调函数,React 会在每次组件渲染后调用它。
  • 第二个参数是一个依赖数组,当数组中的值发生变化时,副作用才会再次执行。
  • 返回的函数(如果有的话)是清理函数,在组件卸载前或下一次副作用执行前被调用。

结合 App 组件分析 useEffect 的典型用法

示例代码片段(来自 App.js):

useEffect(() => {
  console.log('只在组件挂载时运行一次!!!');
  const fetchRepos = async () => {
    const response = await fetch('https://api.github.com/users/Srierin/repos');
    const data = await response.json();
    setRepos(data);
  };
  fetchRepos();
}, []);

解析:

  • 该副作用只在组件首次渲染后执行一次(依赖数组为空)。
  • 内部定义了一个异步函数 fetchRepos,请求 GitHub API 并更新 repos 状态。
  • 更新后的 repos 会触发组件重新渲染,从而在页面上展示获取到的仓库列表。

⚠️ 注意:不能直接将 useEffect 设为 async,因为 useEffect 要求返回的是一个清理函数,而不是 Promise。

详细解释:

  • console.log('只在组件挂载时运行一次!!!'):仅在组件首次加载时打印这条消息。
  • const fetchRepos = async () => {...}:定义一个异步函数,负责发起网络请求并设置响应结果到状态中。
  • fetchRepos();:立即调用这个异步函数。
  • }, []);:空数组表示此 useEffect 只在组件首次加载时运行。

🕒 Timer 组件详解:useEffect 的完整生命周期控制

示例代码片段(来自 Timer.js):

import { useEffect, useState } from 'react';

const Timer = () => {
  const [time, setTime] = useState(0);

  console.log('组件函数执行');
  console.log('JSX编译');

  useEffect(() => {
    console.log('组件渲染完了');
    const interval = setInterval(() => {
      setTime(prevTime => prevTime + 1);
    }, 1000);

    return () => {
      console.log('组件卸载');
      clearInterval(interval);
    };
  }, []);

  return (
    <div>已经运行{time}秒</div>
  );
};

export default Timer;

执行流程分析:

  1. 组件函数执行阶段

    • console.log('组件函数执行')
    • console.log('JSX编译')
    • 初始化 time 状态为 0
    • 构建并返回 JSX 模板
  2. useEffect 生命周期控制

    • 组件首次渲染完成后执行副作用函数:
      • 启动定时器,每秒钟更新时间
      • 控制台输出 '组件渲染完了'
    • 返回一个清理函数,在组件卸载前执行:
      • 清除定时器,防止内存泄漏
      • 控制台输出 '组件卸载'
  3. 依赖数组的作用

    • 由于依赖数组为空 [],副作用只在组件挂载时执行一次,避免重复启动定时器。

详细解释:

  • const [time, setTime] = useState(0);:初始化计时器的时间为 0 秒。
  • console.log('组件函数执行');console.log('JSX编译');:这两行代码帮助我们理解组件函数执行的过程。
  • useEffect(() => {...}, []);:此 useEffect 在组件挂载时启动一个定时器,每秒增加 time 状态,并在组件卸载时清除定时器。
  • return <div>已经运行{time}秒</div>;:返回 JSX,展示当前计时器的时间。

🔄 整合 App.js 和 Timer.js

完整代码示例(App.js):

import { useState, useEffect } from 'react';
import './App.css';
import Timer from './components/Timer';

function App() {
  const [count, setCount] = useState(0);
  const [num, setNumber] = useState(0);
  const [repos, setRepos] = useState([]);
  const [isTimerOn, setIsTimerOn] = useState(true);

  useEffect(() => {
    console.log('只在组件挂载时运行一次!!!');
    const fetchRepos = async () => {
      const response = await fetch('https://api.github.com/users/Srierin/repos');
      const data = await response.json();
      console.log(data);
      setRepos(data);
    }
    fetchRepos();
  }, []);

  return (
    <>
      {count}
      <button onClick={() => setCount(count + 1)}>点我</button>
      <br />
      {num}
      <button onClick={() => setNumber(num + 1)}>点我</button>
      <ul id="repos">
        {repos.map(repo => <li key={repo.id}>{repo.full_name}</li>)}
      </ul>
      {isTimerOn && <Timer />}
      <button onClick={() => setIsTimerOn(!isTimerOn)}>toggle timer</button>
    </>
  );
}

export default App;

完整代码示例(Timer.js):

import { useEffect, useState } from 'react';

const Timer = () => {
  const [time, setTime] = useState(0);

  console.log('组件函数执行');
  console.log('JSX编译');

  useEffect(() => {
    console.log('组件渲染完了');
    const interval = setInterval(() => {
      setTime(prevTime => prevTime + 1);
    }, 1000);

    return () => {
      console.log('组件卸载');
      clearInterval(interval);
    };
  }, []);

  return (
    <div>已经运行{time}秒</div>
  );
};

export default Timer;

🎯 结语

通过这个完整的示例项目,我们不仅掌握了 useStateuseEffect 的基本用法,还理解了它们在真实项目中的应用场景和注意事项。

React Hooks 让函数组件拥有了类组件的能力,同时也带来了更清晰、更简洁的编程方式。掌握好 useEffect,不仅能写出更健壮的组件逻辑,还能更好地理解和运用 React 的响应式编程模型。


如果你喜欢这篇文章,欢迎点赞、收藏、分享给更多开发者朋友!如果你有更多关于 React Hooks 的疑问,也可以留言交流 😊