Skip to content
第 3 章 前端 ⏱ 14 分钟阅读

第 3 章:组件与 Props ​

学习目标 ​

  • 理解组件是 React 应用的最小单元
  • 掌握函数组件的定义、组合、嵌套
  • 熟练使用 Props(类型、默认值)
  • 区分 Props 与 State,避开 4 个常见坑

一、组件是什么? ​

组件 = 返回 JSX 的函数(函数组件是主流)。把 UI 拆成独立、可复用的片段。

tsx
function Welcome(props: { name: string }) {
  return <h1>你好,{props.name}</h1>;
}

特点:

  • 大写开头(必须!)
  • 返回单个根元素(或 Fragment)
  • 可以被其他组件引用

二、定义组件 ​

函数组件(推荐) ​

tsx
import type { ReactNode } from 'react';

interface CardProps {
  title: string;
  children: ReactNode;        // 插槽
}

function Card({ title, children }: CardProps) {
  return (
    <div className="card">
      <h3>{title}</h3>
      <div className="card-body">{children}</div>
    </div>
  );
}

箭头函数(简洁) ​

tsx
const Card = ({ title, children }: CardProps) => (
  <div className="card">
    <h3>{title}</h3>
    <div className="card-body">{children}</div>
  </div>
);

⚠️ 坑 1:箭头函数组件在 DevTools 里显示为 Anonymous,调试不方便,复杂组件用 function 声明。

三、使用组件 ​

tsx
function App() {
  return (
    <div>
      <Card title="用户协议">
        <p>请仔细阅读本协议...</p>
      </Card>
      <Card title="隐私政策">
        <p>我们如何保护你的数据...</p>
      </Card>
    </div>
  );
}

<Card> 之间夹的内容会自动作为 children 传入,这就是插槽机制。

四、Props 详解 ​

基础类型 ​

tsx
interface UserCardProps {
  name: string;                  // 必填
  age?: number;                  // 可选(?)
  role: 'admin' | 'user';        // 字面量联合
  onClick: () => void;           // 回调函数
  data: { id: number; name: string };  // 对象
}

function UserCard({ name, age = 18, role, onClick, data }: UserCardProps) {
  return (
    <div onClick={onClick}>
      {name}({age}) - {role} - {data.id}
    </div>
  );
}

默认值 ​

tsx
function Button({ type = 'primary', disabled = false, children }: ButtonProps) {
  return <button className={`btn btn-${type}`} disabled={disabled}>{children}</button>;
}

展开传参 ​

tsx
const props = { type: 'danger', disabled: true, children: '删除' };
<Button {...props} />

children 不止是元素 ​

tsx
<Form>
  <input />
  <button>提交</button>
  {() => console.log('函数 children')}    {/* 渲染函数,高级用法 */}
</Form>

五、组合 vs 继承 ​

React 推荐组合,反对继承。需要复用逻辑用:

  • 组件嵌套(插槽)
  • 自定义 Hook(第 15 章)
  • Render Props
tsx
// 错误:继承(React 不推荐)
class MyButton extends Button {}

// 正确:组合
function Dialog({ title, body, footer }: DialogProps) {
  return (
    <div className="dialog">
      <h2>{title}</h2>
      <div>{body}</div>
      <div className="footer">{footer}</div>
    </div>
  );
}

// 使用
<Dialog
  title="提示"
  body={<p>确定删除?</p>}
  footer={<><button onClick={cancel}>取消</button><button onClick={ok}>确定</button></>}
/>

六、Props 是只读的 ​

函数组件不能修改 props:

tsx
function Bad({ count }: { count: number }) {
  count = count + 1;     // ⚠️ TypeScript 报错,React 不允许
  return <div>{count}</div>;
}

要改值,得用 State(第 4 章)。

⚠️ 坑 2:不要在子组件里改 props,要改就触发父组件的回调(onChange(newValue)),让父组件更新 state 再传下来。

七、key 与 props 的关系 ​

渲染列表时 React 用 key 匹配前后元素,不要把信息塞进 key:

tsx
// 错误:用 index 当 key,顺序变就乱
{items.map((item, i) => <Item key={i} {...item} />)}

// 正确:用业务 ID
{items.map(item => <Item key={item.id} {...item} />)}

⚠️ 坑 3:同一层级兄弟元素的 key 必须唯一,但不同父组件可以有相同 key。

八、本章小结 ​

要点关键
组件定义函数返回 JSX,大写开头
Props 类型用 interface 定义,可选用 ?
默认值解构时 =,或 defaultProps(旧)
children自动接收标签之间内容
组合 vs 继承优先组合(嵌套 / 插槽)
Props 只读不能在子组件修改,只能通过回调上报

动手练习 ​

  1. Card 组件:写 <Card title="...">{children}</Card>,传入不同内容测试
  2. Avatar 组件:接收 src、size、alt,用对象解构 + 默认值
  3. 可配置 Button:支持 type(primary|danger|default)、disabled、onClick,传不同 props 看效果
  4. 找 bug:下面代码哪里错?
    tsx
    function Item({ text }: { text: string }) {
      text = text.toUpperCase();
      return <li>{text}</li>;
    }

下一章:第 4 章:State →

本站基于 VitePress 构建 · 由 StackHub 团队维护