React 函数组件

掌握了 JSX,我们来看它的"宿主"——组件。在 React 里,组件是代码复用和组织的核心单位。好消息是:React 的组件定义极其简单,简单到一句话能说完——"一个返回 JSX 的函数就是一个组件"

1. 定义第一个组件

组件就是一个普通的 JS 函数,满足两个条件:(1) 函数名首字母大写;(2) 返回一段 JSX。

// 一个最简单的组件:就是一个返回 JSX 的函数
function Welcome() {
  return <h1>你好,欢迎学习 React!</h1>;
}

// 用法和 HTML 标签一样,像写自定义标签
function App() {
  return (
    <div>
      <Welcome />
      <Welcome />
    </div>
  );
}

这就是一个完整的 React 组件了。在别的组件里,你可以像写 HTML 标签一样使用它:<Welcome />。React 看到这个标签,就会去调用 Welcome 函数,把它返回的 JSX 渲染到那个位置。

2. 命名规则:首字母必须大写

这是 React 的硬性约定,违反会出 bug:

// ✅ 正确:组件名首字母大写(PascalCase)
function UserCard() { ... }
function ShoppingCart() { ... }

// ❌ 错误:首字母小写,React 会当成原生 HTML 标签
function userCard() { ... }   // 不会被识别为组件

// 原因:React 看到大写字母开头的标签
// → 当作"自定义组件"渲染
// 看到小写字母开头
// → 当成原生 HTML(div/span)渲染

这个规则的根本原因,是 React 需要一种方式区分"原生 HTML 标签"(div、span、h1)和"自定义组件"(Welcome、UserCard)。它选了最简单的:首字母大小写。记住这条,你就再也不会写出"组件不渲染"的 bug。

3. 组件组合:搭积木

React 的精髓是组合(composition):小组件拼成大组件,大组件拼成页面。整个 React 应用,本质上就是一棵"组件树"。

// 定义几个小组件
function Avatar() {
  return <img src="/me.jpg" className="avatar" alt="头像" />;
}

function UserName() {
  return <span className="name">小明</span>;
}

// 把它们组合成一个更大的组件
function UserCard() {
  return (
    <div className="card">
      <Avatar />
      <UserName />
    </div>
  );
}

// 再组合进整个页面
function App() {
  return (
    <main>
      <UserCard />
      <UserCard />
      <UserCard />
    </main>
  );
}

这种写法的好处显而易见:每个组件只管自己那一小块,改头像不影响评论,改评论不影响购物车。高内聚、低耦合——软件工程的黄金法则,React 天然支持。

4. 抽取组件:把大块拆成小块

新手常犯的错误是:把所有东西塞进一个巨大的 App 组件,几百行 JSX,改起来痛苦。正确的做法是主动抽取——看到一个独立的视觉块(用户卡片、评论、按钮组),就把它拆成独立组件。

// ❌ 反例:一个"巨型组件"什么都干,难维护
function Comment() {
  return (
    <div className="comment">
      <div className="user-info">
        <img src="/me.jpg" alt="头像" />
        <div>小明</div>
      </div>
      <div className="text">这篇文章真好</div>
      <div className="date">2024-01-01</div>
    </div>
  );
}

// ✅ 重构:抽出独立组件,职责单一
function UserInfo({ name, avatar }) {
  return (
    <div className="user-info">
      <img src={avatar} alt="头像" />
      <div>{name}</div>
    </div>
  );
}

function Comment({ name, avatar, text, date }) {
  return (
    <div className="comment">
      <UserInfo name={name} avatar={avatar} />
      <div className="text">{text}</div>
      <div className="date">{date}</div>
    </div>
  );
}

判断什么时候该抽组件,有个简单的经验法则:如果一个 JSX 块在别处还会复用,或者它有自己独立的"职责"(如展示用户信息),就抽出来。抽出来的组件命名要有意义(UserInfo 而不是 Box1),让人看名字就懂它干什么。

5. 组件是纯函数(重要理念)

React 期望你的函数组件是纯函数:相同的输入(props)永远返回相同的输出,且不产生副作用(不修改外部变量、不发请求、不改 DOM)。

// 纯函数组件:同样的输入(props)→ 同样的输出(JSX)
function Greeting({ name }) {
  return <h1>你好,{name}!</h1>;
}

// 调用 100 次结果都一样
// → React 能安全地复用、跳过渲染,性能更好

// ❌ 反例:在组件里修改外部变量(不纯)
let clickCount = 0;
function BadButton() {
  clickCount++;              // 副作用!不要在渲染时做
  return <button>点击 {clickCount}</button>;
}

为什么这么严格?因为 React 可能随时多次调用你的组件(开发模式的 StrictMode 故意调两次),如果组件里有副作用,就会出各种诡异的 bug。需要"副作用"(发请求、改状态、操作 DOM)的场合,React 提供了专门的工具(useStateuseEffect),后面会讲。

6. 不要在组件里定义组件

新手另一个常见错误:把组件定义在另一个组件内部。看起来"内聚",其实是大坑:

7. 一个文件一个组件

社区约定:一个 .jsx 文件只导出一个组件(root 组件 App 例外)。文件名和组件名一致(UserCard.jsx 里导出 UserCard),这样别人 import 时一眼就知道里面是什么。组件多了就建文件夹分类:

小结

组件 = 返回 JSX 的函数,首字母大写。React 应用是组件组合的树。好的组件是纯函数 + 单一职责 + 可复用。下一篇我们看组件之间如何"传数据"——这就是 Props。

← 上一篇 JSX 语法

下一篇 Props 属性

✈️💬