首页 / React 学习笔记 / 03

REACT · Vol.I · LESSON 03 · 核心原语

组件与 Props:前端的「方法」和「参数」

入门核心#核心#组件

组件就是「函数」,Props 就是「参数」

如果你上一篇文章理解到位了,这一篇会非常轻松。React 里最基本的概念「组件」,用 Java 的话说就是——一个函数

UserCard.tsx · 一个函数组件
function UserCard(props) {
  return (
    <div className="card">
      <h3>{props.name}</h3>
      <p>{props.title}</p>
    </div>
  );
}

// 使用:就像调用一个函数
<UserCard name="wan" title="后端工程师" />

它和这个 Java 方法本质上是同一件事:

UserCard.java · 一个 Java 方法
String userCard(String name, String title) {
    return "<h3>" + name + "</h3><p>" + title + "</p>";
}

一个接收「数据」(参数),返回「界面描述」(JSX)的函数,就是 React 组件。就这么简单。

如果组件是函数、Props 是参数,那组件的「返回值」是什么?——是 JSX(界面描述)。所以 React 应用的整个界面,本质是「一堆接收数据、返回界面的函数」层层嵌套组成的树。App 调用 Header,Header 调用 Logo……一路调到底,全是函数调用。

有一个语法细节必须马上记住——组件名必须以大写字母开头

大小写决定「它是谁」
<UserCard />    // ✅ 大写开头 → React 知道这是你的组件函数
<userCard />    // ❌ 小写开头 → React 当成原生 HTML 标签 <usercard>
<div />         // 小写 → 原生标签,这反而是对的

React 就靠「首字母大小写」区分「自定义组件」和「内置标签」。所以 UserCard 不能叫 userCard,这不是代码风格偏好,是语法要求——就像 Java 的类名必须能过编译器检查一样硬性。

Props 只读:前端的「不可变参数」约定

React 有一条铁律:组件绝不能修改自己的 Props。对应到 Java,就是「方法参数应当是不可变的」。看这个反例:

❌ 反例:试图修改 props
function UserCard(props) {
  props.name = "hacker";   // ❌ 绝对禁止!
  return <div>{props.name}</div>;
}

为什么不可以?Java 里我们也有过同样的一课——如果方法 A 调方法 B 时把参数传进去,B 却偷偷改了这个参数,A 内部的其它逻辑就会被「看不见地污染」。这种隐性的数据耦合,是所有大型系统的噩梦。

React 用「Props 只读」这条约定,强制数据流保持单向:数据从父组件流向子组件,永远清晰、可追踪。

后端类比:构造注入 vs 可变字段

你可以把 Props 理解为 Spring 的构造器注入:依赖(数据)在创建时传入,之后不可变更;而组件自己的 state(下一篇讲)则对应实例字段——它是组件内部自持、可以变化的数据。

再借一个词:Props 就像 DTO——跨层传递、只读消费;谁要改数据,谁就得回到数据的「源头」去改(改 state),再把新值一路传下来。

那「我想给 props 加个默认值 / 换个名字」总行吧?行——但做法是「在本地另起炉灶」,而不是改参数本身:

解构 + 默认值(推荐写法)
// ✅ 解构出字段,顺便给默认值——完全没碰 props 本身
function UserCard({ name, title = "工程师", avatar }) {
  return (
    <div className="card">
      <img src={avatar} alt={name} />
      <h3>{name}</h3>
      <p>{title}</p>
    </div>
  );
}

// 调用方可以省略 title:
<UserCard name="wan" avatar="/wan.png" />

解构默认值对应 Java 里的「方法重载给缺省参数」,让调用方更省事,组件内部更清爽。props 一多,这种写法还能顺带起到「文档化参数列表」的作用——函数签名一眼看清这个组件吃什么数据。

单向数据流 + 组合,拼出整个应用

既然数据只能从父流向子,那整个应用就是一棵「数据流动的树」:

App state: user Header <Header user={user}/> Profile <Profile user={user}/> Footer <Footer /> 数据沿树自上而下单向流动,绝不逆向
图 1组件树与单向数据流:state 集中在父组件,通过 Props 分发给子组件。

组装(Composition)也简单直接——组件里嵌套组件,像拼积木:

App.tsx · 组合
function App() {
  const user = { name: "wan", title: "后端工程师" };
  return (
    <div>
      <Header user={user} />
      <Profile user={user} />
      <Footer />
    </div>
  );
}

这里藏着一个对 Java 工程师特别亲切的事实:React 没有继承。你永远不会写 class ProfileCard extends BaseCard 这样的东西——复用全靠「组合」:把公共部分抽成组件,然后嵌套使用。这就是那句被说烂但确实成立的话:组合优于继承。你在后端推崇的「多用组合、少用继承」,在前端被贯彻得更彻底。

特殊的孩子:children

有一类 props 不需要显式声明——写进标签「肚子里」的内容,会自动装进一个叫 children 的 prop:

Card.tsx · 用 children 做「容器组件」
function Card({ title, children }) {
  return (
    <div className="card">
      <h3>{title}</h3>
      <div className="card-body">{children}</div>
    </div>
  );
}

// 使用:标签之间的任何东西,都是 children
<Card title="个人信息">
  <p>姓名:wan</p>
  <p>职业:后端工程师</p>
</Card>

这让你能写出「外壳固定、内容随意」的容器组件——卡片、弹窗、布局框全靠它。对应 Java 里的「模板方法模式」:骨架我定,填充物你给。

把「函数」也当 props 传

props 能传任何值,自然也能传函数。这是子组件「向父组件汇报」的标准通道:

回调 props:子组件喊父组件干活
function DeleteButton({ onDelete }) {
  // 子组件不知道也不关心「删除」具体怎么做,
  // 它只负责在合适的时机调用传进来的函数
  return <button onClick={onDelete}>删除</button>;
}

// 父组件掌握实际逻辑:
<DeleteButton onDelete={() => removeUser(user.id)} />

注意数据流依然是单向的:数据向下流(props),动作向上传(回调函数)。子组件不直接改任何东西,它只是「通知」父亲:嘿,该你了。这个模式在第 8 篇 Todo 实战里会大量出现,卷二讲「状态提升」时它还是主角。

什么时候该拆组件?

会写组件之后,下一个问题马上到来:一个页面写成一个巨型组件,还是拆成二十个小件?这条界线画在哪里,直接决定代码的可维护性。给你三条和后端完全同源的判断标准:

判断标准后端世界的对应说法
职责是否单一:这块 JSX 能用一句话描述吗?(「用户头像卡片」「订单状态徽章」)单一职责原则——一个 Service 干一件事
是否会被复用:同样的结构要在两处以上出现?抽取公共工具类 / 基类方法
读起来是否费劲:JSX 超过一屏、嵌套超过三四层、中间隔着大段不相干的逻辑?方法超过 80 行就该拆了

拆出来的组件之间,用 props 把「接缝」定义清楚,就像微服务之间用接口契约通信。一个可参考的手感:小组件 20~50 行,大组件不超过一屏;拆不出有意义的 props 时,先别硬拆——过早拆分会造成一堆只在一处使用、参数签名却复杂得吓人的「伪组件」,和过早抽象一样有害。

拆组件还有一个隐性收益:缩小「重新计算」的范围。父组件重新渲染时,拆出去且 props 没变的子组件,未来有机会被 React 优化跳过(第 37 篇讲)。就像把热点数据拆成独立缓存区——边界清晰了,优化才有着力点。

这一篇要背下来的东西

核心要点
  • 组件 = 接收 Props、返回 JSX 的函数;组件名必须大写开头。
  • Props 是只读的「参数」(对应构造注入 / DTO),解构 + 默认值是标准姿势。
  • 单向数据流:数据向下流(props),动作向上传(回调函数)。
  • children 做容器组件;复用靠组合,没有继承。
  • 拆组件看三点:职责单一、会被复用、读着费劲。

本章回顾

  • 组件 = 函数:输入 Props,输出 JSX。
  • Props = 不可变参数:只能读,不能改(对应构造注入)。
  • 单向数据流:父 → 子,永远自上而下——这会让你的程序好调试、好推理。

下一篇我们讲 state——组件自己的「实例字段」。Props 是别人给的、不能动;state 是自己的、可以变。这对「一静一动」的搭档凑齐,界面才能真正活起来。

Comments · 评论