React组件基础与Props

组件是React应用的核心构建块,而Props是实现组件通信的基础。本章将深入讲解组件的基础概念和Props的全面用法。

什么是React组件?

React组件是独立、可复用的代码块,用于构建用户界面。组件接收输入(props),并返回描述页面展示内容的React元素。

组件概念示意图

组件A
组件B
组件C

组件可以组合、嵌套,形成复杂的用户界面

组件的两种类型

1. 函数组件(Function Components)

使用JavaScript函数定义的组件,是React 16.8之后推荐的主要组件形式。

函数组件示例

// 简单的函数组件
function Welcome(props) {
  return <h1>Hello, {props.name}!</h1>
}

// 箭头函数形式
const Button = ({ label, onClick }) => {
  return (
    <button onClick={onClick} className="btn">
      {label}
    </button>
  );
}

// 使用组件
const App = () => {
  return (
    <div>
      <Welcome name="React开发者" />
      <Button label="点击我" onClick={() => alert('按钮被点击!')} />
    </div>
  );
}
                            

2. 类组件(Class Components)

使用ES6类定义的组件,支持生命周期方法和状态(state)。

类组件示例

import React from 'react';

class Welcome extends React.Component {
  render() {
    return <h1>Hello, {this.props.name}!</h1>
  }
}

class Counter extends React.Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 };
  }

  render() {
    return (
      <div>
        <p>Count: {this.state.count}</p>
        <button onClick={() => this.setState({ count: this.state.count + 1 })}>
          增加
        </button>
      </div>
    );
  }
}
                            
函数组件 vs 类组件
  • 函数组件:简洁、易于测试、推荐使用(配合Hooks)
  • 类组件:功能完整、支持生命周期、需要处理this绑定

理解Props(属性)

Props是组件之间传递数据的机制,它是只读的(immutable)。

Props数据流向

父组件数据
通过Props传递
子组件接收

数据只能从父组件流向子组件(单向数据流)

基本Props用法


// 父组件传递数据
function App() {
  const user = {
    name: '张三',
    age: 28,
    job: '前端工程师'
  };

  return (
    <div>
      <UserProfile
        name={user.name}
        age={user.age}
        job={user.job}
        isAdmin={true}
        onUpdate={(newName) => console.log('更新:', newName)}
      />
    </div>
  );
}

// 子组件接收Props
function UserProfile(props) {
  return (
    <div className="user-profile">
      <h2>用户信息</h2>
      <p>姓名: {props.name}</p>
      <p>年龄: {props.age}</p>
      <p>职业: {props.job}</p>
      {props.isAdmin && <span className="badge bg-danger">管理员</span>}
      <button onClick={() => props.onUpdate(props.name + ' (已更新)')}>
        更新信息
      </button>
    </div>
  );
}
                    

Props解构赋值

使用ES6解构语法让代码更简洁:


// 方式1:在参数中解构
function UserCard({ name, age, email, isVerified }) {
  return (
    <div className="user-card">
      <h3>{name}</h3>
      <p>年龄: {age}</p>
      <p>邮箱: {email}</p>
      {isVerified && <span className="verified">✓ 已认证</span>}
    </div>
  );
}

// 方式2:在函数体中解构
function UserCard(props) {
  const { name, age, email, isVerified } = props;
  return (
    <div className="user-card">
      <h3>{name}</h3>
      <p>年龄: {age}</p>
      <p>邮箱: {email}</p>
      {isVerified && <span className="verified">✓ 已认证</span>}
    </div>
  );
}
                    

默认Props值


// 方式1:使用默认参数值
function Greeting({ name = '访客', greeting = '你好' }) {
  return <h1>{greeting}, {name}!</h1>
}

// 方式2:使用defaultProps属性
function Button({ text, color, size }) {
  return (
    <button className={`btn btn-${color} btn-${size}`}>
      {text}
    </button>
  );
}

Button.defaultProps = {
  text: '按钮',
  color: 'primary',
  size: 'md'
};

// 类组件的defaultProps
class Alert extends React.Component {
  static defaultProps = {
    type: 'info',
    message: '默认消息'
  };

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

特殊Props:children

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


// 容器组件示例
function Card({ title, children }) {
  return (
    <div className="card">
      {title && <div className="card-header">{title}</div>}
      <div className="card-body">
        {children}
      </div>
    </div>
  );
}

// 使用Card组件
function App() {
  return (
    <Card title="用户信息">
      <p>姓名: 李四</p>
      <p>年龄: 25</p>
      <p>邮箱: lisi@example.com</p>
      <button>编辑信息</button>
    </Card>
  );
}
                    

渲染函数作为Children


// 渲染函数作为children
function DataFetcher({ url, children }) {
  const [data, setData] = React.useState(null);
  const [loading, setLoading] = React.useState(true);

  React.useEffect(() => {
    fetch(url)
      .then(response => response.json())
      .then(data => {
        setData(data);
        setLoading(false);
      });
  }, [url]);

  // 将children作为函数调用
  return children({ data, loading });
}

// 使用方式
function App() {
  return (
    <DataFetcher url="https://api.example.com/user">
      {({ data, loading }) => (
        <div>
          {loading ? (
            <p>加载中...</p>
          ) : (
            <ul>
              {data.map(user => (
                <li key={user.id}>{user.name}</li>
              ))}
            </ul>
          )}
        </div>
      )}
    </DataFetcher>
  );
}
                    

PropTypes类型检查

使用PropTypes为组件props添加类型检查(需要单独安装prop-types库)。

PropTypes使用示例

import PropTypes from 'prop-types';

function Product({
  name,
  price,
  isAvailable,
  categories,
  onBuy
}) {
  return (
    <div className="product">
      <h3>{name}</h3>
      <p>价格: ¥{price}</p>
      <p>状态: {isAvailable ? '有货' : '缺货'}</p>
      <div>
        分类: {categories.join(', ')}
      </div>
      <button onClick={onBuy} disabled={!isAvailable}>
        购买
      </button>
    </div>
  );
}

// 定义PropTypes
Product.propTypes = {
  name: PropTypes.string.isRequired,
  price: PropTypes.number.isRequired,
  isAvailable: PropTypes.bool,
  categories: PropTypes.arrayOf(PropTypes.string),
  onBuy: PropTypes.func
};

// 默认值
Product.defaultProps = {
  isAvailable: true,
  categories: []
};
                            
PropTypes类型 说明 示例
PropTypes.string 字符串类型 name: PropTypes.string
PropTypes.number 数字类型 age: PropTypes.number
PropTypes.bool 布尔类型 isActive: PropTypes.bool
PropTypes.array 数组类型 items: PropTypes.array
PropTypes.object 对象类型 user: PropTypes.object
PropTypes.func 函数类型 onClick: PropTypes.func
PropTypes.element React元素 icon: PropTypes.element
.isRequired 必需属性 title: PropTypes.string.isRequired

组件组合与嵌套


// 基础组件
function Button({ children, onClick, variant = 'primary' }) {
  return (
    <button className={`btn btn-${variant}`} onClick={onClick}>
      {children}
    </button>
  );
}

function Card({ children, title }) {
  return (
    <div className="card">
      {title && <div className="card-title">{title}</div>}
      <div className="card-content">{children}</div>
    </div>
  );
}

// 复合组件
function UserDashboard() {
  const users = [
    { id: 1, name: '张三', role: '管理员' },
    { id: 2, name: '李四', role: '编辑' },
    { id: 3, name: '王五', role: '用户' }
  ];

  return (
    <div className="dashboard">
      <Card title="用户管理">
        <div className="user-list">
          {users.map(user => (
            <div key={user.id} className="user-item">
              <span>{user.name}</span>
              <span className="role-badge">{user.role}</span>
              <Button variant="outline" onClick={() => console.log('编辑', user.id)}>
                编辑
              </Button>
            </div>
          ))}
        </div>
        <div className="dashboard-actions">
          <Button variant="success" onClick={() => console.log('添加用户')}>
            添加用户
          </Button>
          <Button variant="danger" onClick={() => console.log('批量删除')}>
            批量删除
          </Button>
        </div>
      </Card>
    </div>
  );
}
                    

Props使用的最佳实践

推荐做法
  • 使用解构赋值提取props
  • 为可选props设置默认值
  • 使用PropTypes进行类型检查
  • 保持props为只读,不直接修改
  • 命名事件处理props为onEvent格式
  • 将复杂组件拆分为更小的组件
避免做法
  • 避免传递过多props(考虑使用Context)
  • 不要直接修改传入的props
  • 避免在组件中直接修改DOM
  • 不要忘记key属性在列表渲染中
  • 避免过深的组件嵌套
  • 不要混用props和state

完整示例:商品列表组件


import PropTypes from 'prop-types';

function ProductItem({
  product,
  onAddToCart,
  onViewDetail
}) {
  const {
    id,
    name,
    price,
    description,
    image,
    stock,
    category
  } = product;

  return (
    <div className="product-item">
      <div className="product-image">
        <img src={image} alt={name} />
        {stock < 5 && <span className="stock-warning">仅剩{stock}件</span>}
      </div>
      <div className="product-info">
        <h3 className="product-name">{name}</h3>
        <p className="product-description">{description}</p>
        <div className="product-meta">
          <span className="product-price">¥{price.toFixed(2)}</span>
          <span className="product-category">{category}</span>
        </div>
        <div className="product-actions">
          <button
            className="btn btn-primary"
            onClick={() => onAddToCart(id)}
            disabled={stock === 0}
          >
            {stock === 0 ? '已售罄' : '加入购物车'}
          </button>
          <button
            className="btn btn-outline-secondary"
            onClick={() => onViewDetail(id)}
          >
            查看详情
          </button>
        </div>
      </div>
    </div>
  );
}

// PropTypes定义
ProductItem.propTypes = {
  product: PropTypes.shape({
    id: PropTypes.number.isRequired,
    name: PropTypes.string.isRequired,
    price: PropTypes.number.isRequired,
    description: PropTypes.string,
    image: PropTypes.string.isRequired,
    stock: PropTypes.number.isRequired,
    category: PropTypes.string.isRequired
  }).isRequired,
  onAddToCart: PropTypes.func.isRequired,
  onViewDetail: PropTypes.func.isRequired
};

// 使用ProductItem的父组件
function ProductList({ products, onAddToCart, onViewDetail }) {
  return (
    <div className="product-list">
      {products.length === 0 ? (
        <p className="empty-message">暂无商品</p>
      ) : (
        <div className="products-grid">
          {products.map(product => (
            <ProductItem
              key={product.id}
              product={product}
              onAddToCart={onAddToCart}
              onViewDetail={onViewDetail}
            />
          ))}
        </div>
      )}
    </div>
  );
}
                    

本章要点总结

  • 组件是React应用的基本构建单元
  • 函数组件简洁,类组件功能完整
  • Props是组件之间通信的主要方式
  • Props是只读的,遵循单向数据流
  • 使用解构赋值和默认值使代码更清晰
  • PropTypes提供运行时类型检查
  • children prop用于传递组件内容