React:组件与状态管理

39 阅读6分钟

前言:React 其实就是搭积木

很多刚接触 React 的同学,一上来就被各种概念吓到了:虚拟 DOM、生命周期、副作用... 听着头都大了。

其实,React 的核心思想超级简单,就三个字:组件化

想象一下你在玩乐高积木。一个城堡(整个网页)是由无数个小的积木块(组件)搭起来的。有的积木块负责显示标题,有的负责显示按钮,有的负责弹窗。

今天,我们就来聊聊怎么造这些积木,以及积木之间怎么“悄悄话”(数据传递)。放心,不讲枯燥的理论,咱们只讲人话


1. 函数组件 vs 类组件:时代的眼泪

在 React 的世界里,造积木有两种方式:函数组件类组件

历史背景:类组件 (Class Component)

以前(React 16.8 之前),如果积木需要有“记忆”(State),我们必须用类组件。它像是一个重装坦克,功能强大但笨重,写起来到处都是 this,让人头晕。

行业标准:函数组件 (Functional Component)

现在,函数组件才是王道!它就是一个普通的 JavaScript 函数。配合 Hooks(钩子),它变得既轻量又强大。

博主建议:如果你是 2024/2025 年开始学 React,请直接梭哈函数组件!类组件能看懂就行,主要用于维护老项目。

一图看懂区别

特性函数组件 (Modern)类组件 (Legacy)
定义方式简单的 JS 函数继承 React.Component 的类
代码量极少,干净清爽冗长,包含构造函数
状态管理useState Hookthis.state / this.setState
性能默认更轻量需要实例化,开销稍大

JavaScript

//  类组件:看着就累
class HelloClass extends React.Component {
  render() {
    return <div>Hello, {this.props.name}</div>;
  }
}

//  函数组件:清爽!
const HelloFunction = ({ name }) => {
  return <div>Hello, {name}</div>;
};

2. 状态 (State) vs 属性 (Props):钱包与信封

这是新手最容易混淆的两个概念。咱们用一个生活中的比喻:

属性 (Props):爸爸给的零花钱

  • 来源:父组件传给子组件的。
  • 特点只读!  就像爸爸给你的钱,你不能自己把 100 块改成 1000 块(会被打断腿),你只能花。
  • 作用:用于父子组件通信。

状态 (State):自己的私房钱

  • 来源:组件自己定义的。
  • 特点可变!  这是你的私有财产,你想怎么变就怎么变。
  • 作用:记录组件内部的变化(比如计数器、输入框的内容)。

重点:当 State 发生变化时,组件会重新渲染(React 会重新画一遍 UI),这样页面就动起来了。

实战代码 (Hooks 写法)

JavaScript

import React, { useState } from 'react';

const Wallet = (props) => {
  // useState 是 React 提供的一个 Hook
  // money: 当前状态的值
  // setMoney: 用来修改状态的函数(遥控器)
  // useState(0): 初始值是 0
  const [money, setMoney] = useState(0);

  const earnMoney = () => {
    //  错误写法:money = money + 10; (React 不知道你改了)
    //  正确写法:使用 setMoney
    setMoney(money + 10);
  };

  return (
    <div style={{ border: '1px solid #ccc', padding: '10px' }}>
      <h3>{props.owner} 的钱包</h3> {/* Props: 爸爸给的名字 */}
      <p>余额: {money} 元</p>       {/* State: 自己的私房钱 */}
      <button onClick={earnMoney}>搬砖赚 10 块</button>
    </div>
  );
};

// 父组件使用
export default function App() {
  return <Wallet owner="小明" />; // 传入 Props
}

3. 受控组件 vs 非受控组件:控制欲强一点比较好

在处理表单(Input)时,React 有两种流派。

受控组件 (Controlled) - 推荐

  • 原理:输入框的值被 React 的 State 完全接管
  • 比喻:像是一个控制欲极强的男/女朋友。你心里想什么(Input 的 value),必须第一时间告诉 TA(Update State),TA 不同意你就不能变。
  • 优点:数据不仅在 DOM 里,也在 React 的 State 里,Single Source of Truth (单一数据源),方便校验和处理。

非受控组件 (Uncontrolled)

  • 原理:数据由 DOM 自己管理,React 需要的时候通过 ref 去拿。
  • 比喻:像是一个放养的对象。平常不管你,只有到结婚(提交表单)的时候,才问你有多少存款。
  • 优点:代码稍微少点,适合集成非 React 的第三方库。

JavaScript

// 受控组件示例(标准写法)
const ControlledInput = () => {
  const [text, setText] = useState('');

  return (
    <input 
      value={text} 
      onChange={(e) => setText(e.target.value)} // 每次输入都同步给 React
    />
  );
};

4. 高阶组件 (HOC) vs 自定义 Hooks:时代的更替

在笔记中我们看到了 HOC (Higher-Order Component)
简单说,HOC 就是一个函数,它吃进去一个组件,吐出来一个增强版的新组件。就像给手机套了一个防摔壳。

HOC 的痛点

虽然 HOC 曾经很流行,但它容易导致“套娃地狱”。打开 React DevTools,你会看到 WithLogging(WithAuth(WithTheme(Component))),看得密集恐惧症都犯了。

现代方案:自定义 Hooks

现在,我们更倾向于使用 Custom Hooks 来复用逻辑。它更直观,没有组件层级的嵌套。

场景:比如我们要记录组件加载的日志。

以前 (HOC) :

JavaScript

const MyComponent = withLogging(BaseComponent); // 包一层

现在 (Hooks) :

JavaScript

// 定义一个自定义 Hook
const useLogging = (componentName) => {
  React.useEffect(() => {
    console.log(`${componentName} 挂载了!`);
  }, []);
};

// 在组件里直接用,没有任何嵌套!
const MyComponent = () => {
  useLogging('MyComponent'); 
  return <div>Hello</div>;
};

5. Context API:穿越时空的传送门

假设你的组件层级是这样的:
爷爷 -> 爸爸 -> 儿子 -> 孙子

如果你想把“爷爷”的一个数据传给“孙子”:

  • 笨办法 (Props Drilling) :爷爷传给爸爸,爸爸传给儿子,儿子传给孙子... 中间的人虽然不用这个数据,但还得帮忙传,累死个人。
  • 聪明办法 (Context) :建立一个传送门。爷爷直接把数据放进传送门,孙子直接从传送门里拿。

快速上手 Context

  1. 创建上下文 (createContext)
  2. 提供数据 (Provider)
  3. 消费数据 (useContext —— 这是 Hook 写法,比旧版 Consumer 爽多了)

JavaScript

import React, { createContext, useContext, useState } from 'react';

// 1. 创建 Context 对象
const ThemeContext = createContext();

// 2. 爷爷组件:提供数据
const GrandFather = () => {
  const [theme, setTheme] = useState('dark');
  
  return (
    // 使用 Provider 包裹,value 就是要传送的数据
    <ThemeContext.Provider value={{ theme, setTheme }}>
      <Father />
    </ThemeContext.Provider>
  );
};

const Father = () => <Son />; // 爸爸不用管 theme
const Son = () => <GrandSon />; // 儿子也不用管

// 3. 孙子组件:直接获取数据
const GrandSon = () => {
  // 一行代码拿到爷爷传下来的数据!
  const { theme, setTheme } = useContext(ThemeContext);
  
  return (
    <div style={{ background: theme === 'dark' ? '#333' : '#fff' }}>
      我是孙子,现在是 {theme} 模式
      <button onClick={() => setTheme('light')}>切换亮色</button>
    </div>
  );
};

🛡️ 新手避坑指南

  1. 永远不要直接修改 State

    •  state.count = 1 —— React 根本不知道你改了,界面不会变。
    •  setState(1) —— 必须用 Setter 函数。
  2. Context 虽好,不要贪杯

    • Context 更新时,所有消费它的子组件都会强制渲染。如果你的 value 变化太频繁,可能会导致性能问题。对于复杂的全局状态,大型项目通常会用 Redux、Zustand 或 Jotai。
  3. Hooks 只能在顶层调用

    • 千万不要在 if、for 循环里写 useState 或 useEffect。React 是靠顺序来记住哪个 State 对应哪个 Hook 的,乱了顺序就崩了。

结语

恭喜你!读到这里,你已经掌握了 React 组件化开发最核心的 80% 内容。

  • 函数组件让你写代码如丝般顺滑。
  • State/Props 帮你管理数据流。
  • Hooks 让你复用逻辑不再套娃。
  • Context 帮你解决跨层级通信。

剩下的 20%,就是要在实战中不断踩坑和填坑了。拿起你的键盘,新建一个 React 项目(npm create vite@latest),试着写一个简单的计数器或者待办事项列表吧!