Props

Props(属性)是组件之间传递数据的方式

什么是 Props?

Props 是你传递给 JSX 标签的属性,类似于 HTML 中的属性。 在 React 中,props 用于将数据从父组件传递到子组件。

TypeScript
// 父组件传递 props
function App() {
  return <Greeting name="Taylor" />;
}

// 子组件接收 props
function Greeting({ name }) {
  return <h1>你好,{name}!</h1>;
}
概念

Props 是只读的。组件不能修改接收到的 props, 这使得数据流更加可预测。

传递 Props

传递字符串

TypeScript
// 使用双引号
<Greeting name="Taylor" />

// 使用模板字符串
<Greeting name={`Taylor`} />

传递动态值

使用花括号 {} 传递 JavaScript 表达式:

TypeScript
function App() {
  const name = 'Taylor';
  const age = 25;

  return (
    <UserProfile
      name={name}
      age={age}
      isAdmin={true}
    />
  );
}

传递对象

TypeScript
const user = {
  name: 'Taylor',
  age: 25,
  email: 'taylor@example.com'
};

// 传递整个对象
<UserProfile user={user} />

// 使用展开运算符传递属性
<UserProfile {...user} />
注意

使用展开运算符时,要确保对象中的属性名与组件期望的 prop 名称一致。

接收 Props

解构参数

解构是最常用和推荐的方式,代码更简洁:

TypeScript
// ✅ 推荐:解构参数
function UserProfile({ name, age, email }) {
  return (
    <div>
      <h2>{name}</h2>
      <p>年龄: {age}</p>
      <p>邮箱: {email}</p>
    </div>
  );
}

使用 props 对象

TypeScript
// 也可以使用完整的 props 对象
function UserProfile(props) {
  return (
    <div>
      <h2>{props.name}</h2>
      <p>年龄: {props.age}</p>
      <p>邮箱: {props.email}</p>
    </div>
  );
}

默认值

使用解构的默认值语法为 props 设置默认值:

TypeScript
function Button({
  variant = 'primary',
  size = 'medium',
  children
}) {
  return (
    <button className={`btn btn-${variant} btn-${size}`}>
      {children}
    </button>
  );
}

// 使用默认值
<Button>点击</Button>

// 覆盖默认值
<Button variant="secondary" size="large">
  点击
</Button>

Children Prop

children 是一个特殊的 prop,用于在组件标签之间传递内容:

TypeScript
function Card({ children }) {
  return (
    <div className="card">
      {children}
    </div>
  );
}

// 使用
<Card>
  <h2>标题</h2>
  <p>内容</p>
</Card>

嵌套组件

TypeScript
function Layout({ header, sidebar, children }) {
  return (
    <div className="layout">
      <header>{header}</header>
      <aside>{sidebar}</aside>
      <main>{children}</main>
    </div>
  );
}

<Layout
  header={<Header />}
  sidebar={<Sidebar />}
>
  <Content />
</Layout>
最佳实践

当你需要创建可复用的容器组件时,children prop 非常有用。 它让组件更加灵活,可以接受任意内容。

Props 穿透

Props 穿透是指通过多层组件传递 props 的过程:

TypeScript
// 父组件
function App() {
  const [theme, setTheme] = useState('light');
  return <Section theme={theme} />;
}

// 中间组件
function Section({ theme }) {
  return <Article theme={theme} />;
}

// 子组件
function Article({ theme }) {
  return <Content theme={theme} />;
}
注意

当 props 需要穿透多层组件时,考虑使用 Context API 来简化数据传递。 Props 穿透会增加组件之间的耦合度。

使用 Context 替代 Props 穿透

TypeScript
const ThemeContext = createContext('light');

function App() {
  return (
    <ThemeContext.Provider value="light">
      <Section />
    </ThemeContext.Provider>
  );
}

function Section() {
  // 不再需要传递 theme
  return <Article />;
}

function Article() {
  const theme = useContext(ThemeContext);
  return <Content theme={theme} />;
}

传递事件处理函数

你可以将事件处理函数作为 props 传递给子组件:

TypeScript
function Button({ onMouseEnter }) {
  return (
    <button
      onMouseEnter={onMouseEnter}
    >
      悬停我
    </button>
  );
}

function App() {
  const handleMouseEnter = () => {
    console.log('按钮被悬停');
  };

  return <Button onMouseEnter={handleMouseEnter} />;
}
命名提示

传递事件处理函数作为 props 时,建议使用与原生事件相同的名称(如 onMouseEnteronClick), 这样更清晰地表明这是一个事件处理函数。

命名约定

事件处理函数的 props 通常以 on 开头,后跟事件名称:

TypeScript
// ✅ 好的命名
<Button onClick={handleClick} />
<Input onChange={handleChange} />
<Form onSubmit={handleSubmit} />

// ❌ 不好的命名
<Button click={handleClick} />
<Input change={handleChange} />

Props 是只读的

组件绝不应该修改接收到的 props:

TypeScript
// ❌ 错误:直接修改 props
function User({ name }) {
  name = name.toUpperCase(); // 不要这样做!
  return <h1>{name}</h1>;
}
TypeScript
// ✅ 正确:使用本地状态
function User({ name }) {
  const [displayName, setDisplayName] = useState(name);

  return <h1>{displayName}</h1>;
}
纯组件

React 组件应该是纯函数:对于相同的输入 (props),总是返回相同的输出 (JSX)。 不修改 props 可以保持组件的可预测性。

TypeScript 类型检查

使用 TypeScript 为 props 定义类型,可以获得更好的开发体验:

TypeScript
interface ButtonProps {
  variant: 'primary' | 'secondary' | 'danger';
  size?: 'small' | 'medium' | 'large';
  onClick: () => void;
  children: React.ReactNode;
}

function Button({
  variant,
  size = 'medium',
  onClick,
  children
}: ButtonProps) {
  return (
    <button
      className={`btn btn-${variant} btn-${size}`}
      onClick={onClick}
    >
      {children}
    </button>
  );
}

常用类型

TypeScript
interface Props {
  // 基本类型
  title: string;
  count: number;
  isActive: boolean;

  // 可选类型
  subtitle?: string;

  // 联合类型
  status: 'success' | 'error' | 'loading';

  // 数组类型
  items: string[];

  // 对象类型
  user: {
    name: string;
    age: number;
  };

  // 函数类型
  onClick: (event: MouseEvent) => void;
  onChange: (value: string) => void;

  // React 节点
  children: React.ReactNode;

  // React 元素
  icon: React.ReactElement;

  // 任意类型(不推荐)
  data: any;
}

常见模式

条件渲染

TypeScript
function Alert({ type, message, show }) {
  if (!show) return null;

  return (
    <div className={`alert alert-${type}`}>
      {message}
    </div>
  );
}

<Alert type="error" message="出错了" show={hasError} />

渲染列表

TypeScript
function List({ items }) {
  return (
    <ul>
      {items.map((item) => (
        <li key={item.id}>
          {item.name}
        </li>
      ))}
    </ul>
  );
}

组合组件

TypeScript
function Tabs({ children }) {
  return <div className="tabs">{children}</div>;
}

function Tab({ label, children }) {
  return <div className="tab">{children}</div>;
}

// 使用
<Tabs>
  <Tab label="Tab 1">内容 1</Tab>
  <Tab label="Tab 2">内容 2</Tab>
</Tabs>
提示

组合组件模式让组件更加灵活,可以实现类似 HTML 的声明式 API。

常见问题

Props 没有更新?

确保你创建了新的对象/数组,而不是修改现有的:

TypeScript
// ❌ 错误:修改现有数组
items.push(newItem);
setItems(items);

// ✅ 正确:创建新数组
setItems([...items, newItem]);

子组件没有重新渲染?

检查是否正确地传递了 props,以及 props 的引用是否发生了变化。

如何传递所有 props?

使用展开运算符传递所有 props:

TypeScript
function StyledButton({ className, ...props }) {
  return (
    <button
      className={`btn ${className}`}
      {...props}
    />
  );
}

相关概念