🌱《React 新手生存指南:Hooks + 基础 API + 组件通信,从“看不懂”到“我也会”》

52 阅读8分钟

你好呀,正在学 React 的朋友 👋!

是不是看到 useStateuseEffect 这些词就头大?
是不是写了个组件,点按钮没反应,控制台还报错?
是不是搞不清“父组件怎么把数据传给子组件”?

别怕!这篇文章 从零开始,手把手带你搞懂 React 最常用、最基础的三块内容

  1. 6 个最常用的 Hooks(怎么用、什么时候用)
  2. 4 个超实用的 React 基础 API(不是 Hook,但天天用)
  3. 组件之间怎么“说话”(父子、兄弟怎么传数据)

全文无废话,全是代码 + 解释 + 场景,建议收藏,边看边敲代码


🔹 第一部分:Hooks —— 函数组件的“超能力”

在 React 中,函数组件本来只能展示 UI,不能有状态、不能发请求
但有了 Hooks,它就能“活”起来!

✅ 所有 Hooks 都要以 use 开头(这是规则!)
✅ 只能在函数组件或自定义 Hook 里用(不能在 if 里、不能在普通函数里)


1️⃣ useState —— 让组件“记住东西”

🎯 作用:

让函数组件拥有可以变化的数据(比如计数器、输入框内容)。

💡 使用方法:

import { useState } from 'react';

function Counter() {
  // 声明一个叫 count 的状态,初始值是 0
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>你点了 {count} 次</p>
      {/* 点击时,调用 setCount 把 count + 1 */}
       setCount(count + 1)}>
        +1
      
    </div>
  );
}

📌 关键点:

  • count 是当前状态值(只读!不能直接改)
  • setCount 是修改状态的函数
  • 调用 setCount 后,React 会自动重新渲染组件,显示新值

❌ 错误写法:count = count + 1 → 不会更新页面!
✅ 正确写法:setCount(count + 1)

⚠️ 小技巧:如果新值依赖旧值,用函数形式更安全

// 推荐(尤其在异步或多次更新时)
setCount(prevCount => prevCount + 1);

2️⃣ useEffect —— 让组件“干点别的事”

🎯 作用:

处理副作用(side effect),比如:

  • 页面加载时发请求
  • 监听键盘事件
  • 修改 document.title
  • 订阅 WebSocket

🧠 “副作用” = 和 UI 渲染无关的操作

💡 使用方法(三种常见场景):

✅ 场景1:组件一加载就执行(类似 componentDidMount)
useEffect(() => {
  console.log('组件挂载了!');
  document.title = '欢迎来到我的页面';
}, []); // ← 注意这个空数组!
  • [] 表示“只在组件第一次加载时运行一次”
✅ 场景2:某个数据变了才执行
useEffect(() => {
  console.log(`用户ID变了,现在是:${userId}`);
  fetchUser(userId); // 比如根据 userId 获取用户信息
}, [userId]); // ← 依赖 userId
  • 只要 userId 改变,这个 effect 就会重新运行
✅ 场景3:组件卸载前清理(比如取消订阅)
useEffect(() => {
  const timer = setInterval(() => {
    console.log('每秒打印一次');
  }, 1000);

  // 返回一个函数,就是“清理函数”
  return () => {
    clearInterval(timer); // 组件消失时,清除定时器
    console.log('定时器已清除');
  };
}, []);

📌 关键点:

  • 依赖数组决定何时执行

    • [] → 只运行一次(挂载/卸载)
    • [a, b] → a 或 b 变了才运行
    • 不写 → 每次渲染都运行(慎用!可能死循环)

🚨 常见错误:

  • 忘记加依赖 → 数据不更新
  • 依赖写错 → 无限请求
    ✅ 解法:安装 ESLint 插件 eslint-plugin-react-hooks,它会自动提醒你!

3️⃣ useRef —— 拿到真实 DOM 或存“私房钱”

🎯 作用:

  • 获取真实的 HTML 元素(比如 ``)
  • 存储一个不会触发重渲染的变量(类似类组件的实例属性)

💡 使用方法:

✅ 拿 DOM 元素(聚焦输入框)
import { useRef } from 'react';

function TextInput() {
  const inputRef = useRef(null); // 创建一个 ref

  const focusInput = () => {
    inputRef.current.focus(); // 调用原生 DOM 方法
  };

  return (
    <>
      {/* 把 ref 绑定到 input 上 */}
      
      点我聚焦输入框
    
  );
}
✅ 存变量(比如记录渲染次数)
const renderCount = useRef(0);

// 每次渲染都 +1,但不会导致组件重新渲染!
renderCount.current = renderCount.current + 1;

console.log('渲染了', renderCount.current, '次');

📌 关键点:

  • ref.current 是可读可写的
  • 改 ref.current 不会触发组件重新渲染

4️⃣ useContext —— 全局传值,不用一层层 props

🎯 作用:

解决“爷爷传孙子,中间爸爸只是个传话筒”的问题(专业叫法:props drilling)

💡 使用方法(三步走):

第一步:创建 Context
// context/UserContext.js
import { createContext } from 'react';

// 创建一个 Context,初始值是 null
export const UserContext = createContext(null);
第二步:在顶层用 Provider 包裹,并传值
// App.js
import { UserContext } from './context/UserContext';

function App() {
  const user = { name: '小明', id: 123 };

  return (
    {/* 把 user 传给所有子组件 */}
    
      <header/>
      <main/>
    
  );
}
第三步:在任意子组件中使用
// Header.js
import { useContext } from 'react';
import { UserContext } from './context/UserContext';

function Header() {
  const user = useContext(UserContext); // ← 拿到 user!

  return <h1>欢迎你,{user?.name}!</h1>;
}

📌 关键点:

  • 适合全局共享数据:用户信息、主题、语言等
  • ❌ 不适合频繁变化的状态(会导致所有用到的组件重渲染)

5️⃣ useCallback —— 让函数“稳定不变”

🎯 作用:

防止函数在每次渲染时都生成新引用,避免子组件不必要重渲染

💡 使用场景:

当你把函数作为 prop 传给 React.memo 包裹的子组件时。

// 父组件
import { useCallback } from 'react';

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

  // 用 useCallback 包裹,只有 count 变了才生成新函数
  const handleClick = useCallback(() => {
    console.log('点击了,当前 count 是', count);
  }, [count]);

  return (
    <div>
      <p>Count: {count}</p>
       setCount(c => c + 1)}>+1
      {/* 传给子组件 */}
      
    </div>
  );
}

// 子组件用 React.memo 优化
const ChildButton = React.memo(({ onClick }) => {
  console.log('ChildButton 渲染了'); // 如果没用 useCallback,每次父组件更新都会打印
  return 点我;
});

📌 关键点:

  • useCallback(fn, deps) ≈ useMemo(() => fn, deps)
  • 只有当子组件被 memo 优化时才有意义

6️⃣ useMemo —— 缓存计算结果,省电又省心

🎯 作用:

避免重复执行耗时的计算

💡 使用方法:

import { useMemo } from 'react';

function ExpensiveList({ list, filter }) {
  // 只有 list 或 filter 变了,才重新过滤
  const filteredList = useMemo(() => {
    console.log('执行了过滤(很慢的操作)');
    return list.filter(item => item.includes(filter));
  }, [list, filter]);

  return (
    <ul>
      {filteredList.map(item => <li>{item}</li>)}
    </ul>
  );
}

📌 关键点:

  • 适合:大数据过滤、排序、复杂对象生成
  • ❌ 简单计算(如 a + b)没必要用,反而增加开销

🔹 第二部分:React 基础 API(不是 Hook,但超常用)

这些是 React 提供的工具函数,帮你优化性能、组织代码。


✅ React.memo() —— “你没变就别重渲染!”

🎯 作用:

包裹一个组件,让它只有 props 变了才重新渲染

const UserInfo = React.memo(({ name, age }) => {
  console.log('UserInfo 渲染了');
  return <div>{name}, {age}岁</div>;
});

💡 默认做浅比较(只比第一层 props 是否相等)

⚠️ 注意:

如果 props 里有对象或函数,记得用 useMemo / useCallback 固定引用,否则浅比较会失败!


✅ React.lazy + Suspense —— 懒加载组件

🎯 作用:

按需加载组件,减少首屏 JS 体积

import { lazy, Suspense } from 'react';

// 动态导入组件
const ProfilePage = lazy(() => import('./ProfilePage'));

function App() {
  return (
    <div>
      <nav>导航栏</nav>
      {/* 用 Suspense 包裹,loading 时显示 fallback */}
      加载中...</div>}>
        
      
    </div>
  );
}

✅ 适合:路由页面、弹窗、非首屏内容


✅ Fragment<> )—— 无痕包裹

🎯 作用:

返回多个元素,不产生额外 DOM 节点

function ListItem() {
  return (
    <> {/* 等价于  */}
      <dt>标题</dt>
      <dd>内容</dd>
    
  );
}

💡 在列表、表格、定义列表中特别有用!


✅ createContext —— Context 的“出生证明”

前面 useContext 已经讲过,这里再强调:

const MyContext = React.createContext(defaultValue);
  • defaultValue 只在没有 Provider 时生效
  • 通常和 .Provider 配合使用

🔹 第三部分:组件通信 —— 它们是怎么“说话”的?

React 遵循 “单向数据流” :数据从上往下传。


1️⃣ 父 → 子:用 props

// 父组件
function App() {
  const message = &#34;Hello from parent!&#34;;
  return ;
}

// 子组件
function Child({ text }) {
  return <p>{text}</p>; // 显示 &#34;Hello from parent!&#34;
}

✅ 最安全、最清晰的方式!


2️⃣ 子 → 父:用 回调函数(callback)

// 父组件
function App() {
  const [data, setData] = useState('');

  const handleChildData = (childData) => {
    setData(childData); // 接收子组件传来的数据
  };

  return ;
}

// 子组件
function Child({ onSendData }) {
  const handleClick = () => {
    onSendData(&#34;我是子组件的数据&#34;); // 调用父组件传来的函数
  };

  return 发送数据给父组件;
}

🧠 思想:父组件把“接收数据的方法”传给子组件,子组件调用它


3️⃣ 兄弟组件 or 跨层级:用 Context

前面已详细讲过,适合全局状态

❌ 不要用 Context 传临时数据(比如表单某一步的值)
✅ 用它传:用户登录状态、主题色、语言偏好


4️⃣ 特殊情况:用 ref + forwardRef(命令式操作)

适用于:聚焦、播放、滚动等操作。

// 子组件
const InputBox = forwardRef((props, ref) => {
  return ;
});

// 父组件
function App() {
  const inputRef = useRef();

  const focusInput = () => {
    inputRef.current.focus(); // 调用子组件的 DOM 方法
  };

  return (
    <>
      
      聚焦输入框
    
  );
}

⚠️ 仅用于操作,不要用它传数据!


🧠 终极总结:一张表搞定所有

名称作用什么时候用注意事项
useState管理状态需要可变数据(计数、输入)用 setX 更新,别直接改
useEffect处理副作用发请求、监听、改 title依赖数组别漏!
useRef拿 DOM / 存变量聚焦、存不触发渲染的值current 可读写
useContext全局传值多层组件共享数据别放高频更新状态
useCallback缓存函数传函数给 memo 子组件配合 React.memo 用
useMemo缓存计算大数据过滤、排序简单计算别用
React.memo优化渲染子组件 props 不常变注意函数/对象引用
React.lazy懒加载路由、大组件配合 Suspense

🌈 结语:你已经比昨天的自己更懂 React 了!

React 看似复杂,其实核心思想很简单:
UI = f(state) —— 页面是状态的函数。

只要你理解了:

  • 状态怎么变(useState
  • 副作用怎么处理(useEffect
  • 组件怎么传数据(props / Context)

你就已经掌握了 90% 的日常开发!

📌 建议:把本文当“字典”,写代码卡壳时回来查一查。


彩蛋口诀
状态用 state,副作用靠 effect,
全局用 context,DOM 用 ref 拿,
函数用 callback,计算用 memo,
父子靠 props,跨层靠 context,
懒加载用 lazy,包裹用 fragment!