🚀 React Hooks 入门:让小白也能秒懂的保姆级教程

107 阅读5分钟

🧠 1. useState:状态管理的“瑞士军刀”

image.png

想象你点了一杯奶茶,useState 就像店员记订单的小本本,每次你喊“加糖”“少冰”,它都会更新订单状态!

💡 基本用法:单状态 vs 多状态

// 单状态管理(像一杯纯奶茶)
const [count, setCount] = useState(0); // 初始值为0

// 多状态管理(像奶茶+配料)
const [num, setNumber] = useState(0);
const [repos, setRepos] = useState([]); // 空数组
const [isTimerOn, setIsTimerOn] = useState(true);

📌 注意:每个 useState 都是独立的状态变量,就像奶茶店的每个订单都有自己的编号!

⚠️ 函数式状态更新

// 正确做法(用“上一秒的糖分”计算新糖分)
setTime(prevTime => prevTime + 1); 

// 错误做法(直接写死值)
setTime(time + 1); // 可能会拿到过时的 time 值!

正确示范:用 prevTime => ... 保证拿到最新值,就像根据当前温度调空调,而不是看昨天的温度计!
错误示范:直接写 time + 1 可能导致“奶茶永远少冰”的尴尬。

🧠 为什么需要函数式更新?

React 的状态更新是异步的,直接使用 time + 1 可能导致读取到旧值。比如:

// 错误示例:多次点击按钮时值会出错
setTime(time + 1);
setTime(time + 1);

💡 技巧:用函数式更新可以确保每次操作都基于最新的状态值!


💡 2. useEffect:组件的“小跟班”

image.png

你的组件是个“演员”,useEffect 是它背后的“经纪人”,负责安排开拍(挂载)、换场(更新)、收工(卸载)!

🎬 三幕剧:挂载、更新、卸载

// 挂载时只执行一次(空数组 = 开拍不换场)
useEffect(() => {
  console.log('组件挂载完成!'); // 🎬 开拍啦
}, []); 

// 更新时触发(依赖数组 = 换场条件)
useEffect(() => {
  console.log('count 改变啦!'); // 🔄 换场道具
}, [count]); 

// 卸载时执行(return 清理函数)
useEffect(() => {
  return () => {
    console.log('组件卸载'); // 🧹 收工清场
  }
}, []);

📌 注意:依赖数组 [] 就像“只拍一次广告”的承诺,避免无限循环!

🧠 定时器与清理函数

useEffect(() => {
  const timer = setInterval(() => ..., 1000);
  return () => clearInterval(timer); // 🧹 关掉所有设备
}, []);

💡 技巧:清理函数就像“还租来的设备”,否则组件卸载后定时器会偷偷运行,造成“内存泄漏”!

⚠️ 依赖数组的陷阱

// 错误示例:忘记添加依赖项
useEffect(() => {
  console.log('count 改变啦!'); // ❌ 如果 count 变了,这里不会触发
}, []); 

正确示范:确保依赖数组包含所有影响副作用的变量!


⚠️ 3. 常见坑点避雷指南

image.png

❌ 错误示范:异步请求直接写在组件函数中

// ❌ 错误:请求会和渲染“打架”
fetchData(); // 可能重复请求!

正确示范:把异步请求塞进 useEffect

useEffect(() => {
  const fetchRepos = async () => {
    const data = await fetch(...); // 🚀 安静地请求数据
    setRepos(data);
  };
  fetchRepos();
}, []); // 🛡️ 依赖数组确保只请求一次

🤯 “猜猜看”环节:这段代码会报错吗?

useEffect(async () => {
  await fetch(...); // ❗️不能直接用 async!
}, []);

答案:会报错!useEffect 不能直接写 async,就像不能让经纪人自己当演员!解决方法:在内部定义 async 函数。

⚠️ 依赖数组的“动态更新”

// 错误示例:依赖项未更新
useEffect(() => {
  console.log('count 改变啦!'); 
}, [count]); 

// 正确示例:确保依赖项动态更新
const [count, setCount] = useState(0);

💡 技巧:React 会自动追踪依赖项的变化,但需要你显式声明!


✅ 4. 条件渲染:按需“开关组件”

image.png

{isTimerOn && <Timer />} {/* 用布尔值控制显示 */}
<button onClick={() => setIsTimerOn(!isTimerOn)}>
  toggle timer
</button>

💡 技巧:条件渲染就像“遥控器开关”,isTimerOntrue 时显示组件,false 时彻底隐藏!

🧠 条件渲染的高级用法

{isTimerOn ? <Timer /> : <div>Timer 已关闭</div>}

📌 注意:三元运算符 ? : 可以实现更复杂的条件逻辑!


🧪 5. 代码实战:从 App.jsxTimer.jsx 看核心逻辑

image.png

📌 App.jsx 中的 useEffect 挂载请求

useEffect(() => {
  const fetchRepos = async () => {
    const data = await fetch(...); 
    setRepos(data);
  };
  fetchRepos();
}, []); // 🚦 空数组 = 只执行一次

🧠 逻辑:组件挂载时自动请求数据,像“开机自启动”功能。

📌 Timer.jsx 中的定时器与清理

useEffect(() => {
  const interval = setInterval(() => {
    setTime(prev => prev + 1); // 🔄 函数式更新
  }, 1000);
  return () => clearInterval(interval); // 🧹 清理
}, []);

⚠️ 警告:忘记清理定时器会导致组件卸载后继续运行,像“关不掉的吸尘器”!


🎁 结尾彩蛋


🌟 高级技巧:useEffect 的深度解析

🧠 深入理解依赖数组

useEffect(() => {
  console.log('count 改变啦!'); 
}, [count]); 

📌 注意:依赖数组中的变量如果是一个对象或数组,即使内容变化,React 也不会重新执行副作用(因为引用地址没变)。

⚠️ 如何处理对象或数组的依赖项?

const [user, setUser] = useState({ name: "Alice" });
useEffect(() => {
  console.log(user.name); 
}, [user]); // ❌ 如果 user 对象未被重新创建,副作用不会触发

正确做法:使用 useReducerJSON.stringify 处理复杂数据!


🛠️ 实战演练:完整代码示例

📌 App.jsx 完整代码

import { useEffect, useState } from 'react';
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(() => {
    const fetchRepos = async () => {
      const response = await fetch('https://api.github.com/users/WildBlue58/repos');
      const data = await response.json();
      setRepos(data);
    };
    fetchRepos();
  }, []);

  return (
    <>
      <h1>React Hooks 示例</h1>
      <p>当前计数: {count}</p>
      <button onClick={() => setCount(prev => prev + 1)}>点我</button>
      <ul>
        {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.jsx 完整代码

import { useState, useEffect } from 'react';

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

  useEffect(() => {
    const interval = setInterval(() => {
      setTime(prev => prev + 1);
    }, 1000);
    return () => clearInterval(interval);
  }, []);

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

export default Timer;

🎓 总结:React Hooks 的学习路径

  1. 基础掌握:先学会 useStateuseEffect 的基本用法。
  2. 进阶理解:深入依赖数组、清理函数和异步请求的最佳实践。
  3. 实战应用:通过项目练习巩固知识,比如实现一个计数器或天气应用。
  4. 持续学习:关注 React 官方文档和社区,了解新特性(如 useReduceruseContext)。

💡 记住:React Hooks 就像“奶茶店的智能系统”,学会它,你就是店长!🥤✨