React学习教程,从入门到精通,React 使用属性(Props)创建组件语法知识点与案例详解(15)

发布于:2025-09-10 ⋅ 阅读:(16) ⋅ 点赞:(0)

React 使用属性(Props)创建组件语法知识点与案例详解

一、Props 基础概念

Props(Properties 的缩写)是 React 组件接收外部数据的主要方式,用于父组件向子组件传递数据。

核心特点:

  1. 只读性:props 是只读的,组件不能修改自己的 props
  2. 单向数据流:数据只能从父组件流向子组件
  3. 可传递任意类型:字符串、数字、数组、对象、函数、JSX元素等
  4. 默认值支持:可以设置默认的 prop 值
  5. 类型检查:可以使用 PropTypes 或 TypeScript 进行类型验证

二、Props 语法知识点大全

1. 基本语法

// 父组件传递 props
<ChildComponent propName="value" />

// 子组件接收 props
function ChildComponent(props) {
  return <div>{props.propName}</div>;
}

// 或使用解构赋值
function ChildComponent({ propName }) {
  return <div>{propName}</div>;
}

2. Props 类型

// 字符串
<Component text="Hello World" />

// 数字(需要使用花括号)
<Component count={42} />

// 布尔值
<Component isActive={true} />
<Component isActive /> // 等同于 isActive={true}

// 数组
<Component items={['item1', 'item2', 'item3']} />

// 对象
<Component user={{name: '张三', age: 25}} />

// 函数
<Component onClick={() => console.log('clicked')} />

// JSX元素
<Component header={<h1>标题</h1>} />

// null值
<Component optionalProp={null} />

3. Props 默认值

// 函数组件设置默认值
function MyComponent({ name = '默认名称', age = 18 }) {
  return <div>{name} - {age}岁</div>;
}

// 或使用 defaultProps(适用于类组件和函数组件)
MyComponent.defaultProps = {
  name: '默认名称',
  age: 18
};

4. Props 类型检查(PropTypes)

import PropTypes from 'prop-types';

function MyComponent({ name, age, onClick }) {
  return (
    <div onClick={onClick}>
      {name} - {age}岁
    </div>
  );
}

MyComponent.propTypes = {
  name: PropTypes.string.isRequired,    // 必填字符串
  age: PropTypes.number,                // 可选数字
  onClick: PropTypes.func               // 函数
};

5. Children Props

// 父组件
<ParentComponent>
  <p>这是子元素内容</p>
  <span>可以有多个子元素</span>
</ParentComponent>

// 子组件接收 children
function ParentComponent({ children }) {
  return <div className="container">{children}</div>;
}

6. Spread Operator(展开运算符)

// 将对象的所有属性作为 props 传递
const props = { name: '张三', age: 25, city: '北京' };
<MyComponent {...props} />

// 合并多个 props
const baseProps = { className: 'btn' };
const extraProps = { onClick: handleClick };
<Button {...baseProps} {...extraProps} text="按钮" />

7. 条件 Props

// 根据条件传递不同的 props
const isActive = true;
<MyComponent 
  className={isActive ? 'active' : 'inactive'}
  {...(isActive && { highlight: true })} // 只有 isActive 为 true 时才传递 highlight
/>

三、完整案例代码

import React from 'react';
import PropTypes from 'prop-types';

/**
 * 1. 基础用户信息组件
 * 演示:基本 props 传递、解构赋值、默认值
 */
function UserInfo({ name, age, email, avatarUrl }) {
  return (
    <div className="user-info">
      <img 
        src={avatarUrl} 
        alt={`${name}的头像`} 
        className="avatar"
      />
      <div className="user-details">
        <h3>{name}</h3>
        <p>年龄: {age}岁</p>
        <p>邮箱: {email}</p>
      </div>
    </div>
  );
}

// 设置默认值
UserInfo.defaultProps = {
  name: '匿名用户',
  age: 0,
  email: '未提供邮箱',
  avatarUrl: 'https://via.placeholder.com/50'
};

// 类型检查
UserInfo.propTypes = {
  name: PropTypes.string,
  age: PropTypes.number,
  email: PropTypes.string,
  avatarUrl: PropTypes.string
};

/**
 * 2. 按钮组件
 * 演示:函数 props、布尔 props、条件样式
 */
function Button({ 
  text, 
  onClick, 
  disabled = false, 
  variant = 'primary', 
  size = 'medium',
  fullWidth = false 
}) {
  // 根据 variant 和 size 动态生成 className
  const buttonClass = `btn btn-${variant} btn-${size} ${fullWidth ? 'btn-fullwidth' : ''}`;
  
  return (
    <button 
      className={buttonClass}
      onClick={onClick}
      disabled={disabled}
    >
      {text}
    </button>
  );
}

Button.propTypes = {
  text: PropTypes.string.isRequired,
  onClick: PropTypes.func.isRequired,
  disabled: PropTypes.bool,
  variant: PropTypes.oneOf(['primary', 'secondary', 'danger', 'success']),
  size: PropTypes.oneOf(['small', 'medium', 'large']),
  fullWidth: PropTypes.bool
};

/**
 * 3. 产品卡片组件
 * 演示:对象 props、数组 props、children props
 */
function ProductCard({ product, features, onAddToCart, children }) {
  return (
    <div className="product-card">
      <img 
        src={product.imageUrl} 
        alt={product.name}
        className="product-image"
      />
      <div className="product-info">
        <h3>{product.name}</h3>
        <p className="product-price">¥{product.price}</p>
        <p className="product-description">{product.description}</p>
        
        {/* 渲染特性列表 */}
        {features && features.length > 0 && (
          <div className="product-features">
            <h4>产品特性:</h4>
            <ul>
              {features.map((feature, index) => (
                <li key={index}>{feature}</li>
              ))}
            </ul>
          </div>
        )}
        
        {/* 渲染子元素 */}
        {children && <div className="product-footer">{children}</div>}
        
        <Button 
          text="加入购物车"
          onClick={() => onAddToCart(product)}
          variant="primary"
          fullWidth={true}
        />
      </div>
    </div>
  );
}

ProductCard.propTypes = {
  product: PropTypes.shape({
    id: PropTypes.number.isRequired,
    name: PropTypes.string.isRequired,
    price: PropTypes.number.isRequired,
    description: PropTypes.string,
    imageUrl: PropTypes.string
  }).isRequired,
  features: PropTypes.arrayOf(PropTypes.string),
  onAddToCart: PropTypes.func.isRequired,
  children: PropTypes.node
};

/**
 * 4. 布局组件
 * 演示:children props、多个子元素、插槽模式
 */
function Layout({ header, sidebar, children, footer }) {
  return (
    <div className="layout">
      {header && <header className="layout-header">{header}</header>}
      <div className="layout-body">
        {sidebar && <aside className="layout-sidebar">{sidebar}</aside>}
        <main className="layout-main">{children}</main>
      </div>
      {footer && <footer className="layout-footer">{footer}</footer>}
    </div>
  );
}

Layout.propTypes = {
  header: PropTypes.node,
  sidebar: PropTypes.node,
  children: PropTypes.node.isRequired,
  footer: PropTypes.node
};

/**
 * 5. 表单输入组件
 * 演示:受控组件、事件处理函数 props
 */
function InputField({ 
  label, 
  value, 
  onChange, 
  type = 'text', 
  placeholder = '', 
  error = null,
  required = false 
}) {
  return (
    <div className={`input-field ${error ? 'has-error' : ''}`}>
      {label && (
        <label>
          {label}
          {required && <span className="required">*</span>}
        </label>
      )}
      <input
        type={type}
        value={value}
        onChange={onChange}
        placeholder={placeholder}
        required={required}
        className="form-input"
      />
      {error && <span className="error-message">{error}</span>}
    </div>
  );
}

InputField.propTypes = {
  label: PropTypes.string,
  value: PropTypes.string.isRequired,
  onChange: PropTypes.func.isRequired,
  type: PropTypes.string,
  placeholder: PropTypes.string,
  error: PropTypes.string,
  required: PropTypes.bool
};

/**
 * 6. 主应用组件 - 综合演示
 * 演示:所有 props 用法的综合应用
 */
function App() {
  // 用户数据
  const user = {
    name: '李四',
    age: 28,
    email: 'lisi@example.com',
    avatar: 'https://via.placeholder.com/100'
  };
  
  // 产品数据
  const product = {
    id: 1,
    name: 'React 学习指南',
    price: 89.99,
    description: '全面掌握 React 开发技巧',
    imageUrl: 'https://via.placeholder.com/300x200'
  };
  
  // 产品特性
  const features = [
    '涵盖 React 基础知识',
    '包含 Hooks 详细讲解',
    '实战项目案例',
    '最新 React 18 特性'
  ];
  
  // 状态管理
  const [cartItems, setCartItems] = React.useState([]);
  const [username, setUsername] = React.useState('');
  const [email, setEmail] = React.useState('');
  const [usernameError, setUsernameError] = React.useState('');
  const [emailError, setEmailError] = React.useState('');
  
  // 事件处理函数
  const handleAddToCart = (product) => {
    setCartItems(prev => [...prev, product]);
    alert(`${product.name} 已添加到购物车!`);
  };
  
  const handleUsernameChange = (e) => {
    setUsername(e.target.value);
    // 简单验证
    if (e.target.value.length < 2) {
      setUsernameError('用户名至少2个字符');
    } else {
      setUsernameError('');
    }
  };
  
  const handleEmailChange = (e) => {
    setEmail(e.target.value);
    // 简单邮箱验证
    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    if (!emailRegex.test(e.target.value)) {
      setEmailError('请输入有效的邮箱地址');
    } else {
      setEmailError('');
    }
  };
  
  const handleSubmit = () => {
    if (!usernameError && !emailError && username && email) {
      alert('表单提交成功!');
    }
  };
  
  return (
    <div className="app">
      <h1>React Props 综合示例</h1>
      
      {/* 1. 使用 UserInfo 组件 */}
      <section className="section">
        <h2>用户信息</h2>
        <UserInfo 
          name={user.name} 
          age={user.age} 
          email={user.email} 
          avatarUrl={user.avatar}
        />
      </section>
      
      {/* 2. 使用 Button 组件(多种变体) */}
      <section className="section">
        <h2>按钮示例</h2>
        <div className="button-group">
          <Button 
            text="主要按钮" 
            onClick={() => alert('点击了主要按钮')}
            variant="primary"
          />
          <Button 
            text="次要按钮" 
            onClick={() => alert('点击了次要按钮')}
            variant="secondary"
          />
          <Button 
            text="危险按钮" 
            onClick={() => alert('点击了危险按钮')}
            variant="danger"
          />
          <Button 
            text="大号按钮" 
            onClick={() => alert('点击了大号按钮')}
            size="large"
          />
          <Button 
            text="禁用按钮" 
            onClick={() => alert('点击了禁用按钮')}
            disabled={true}
          />
        </div>
      </section>
      
      {/* 3. 使用 ProductCard 组件 */}
      <section className="section">
        <h2>产品卡片</h2>
        <ProductCard 
          product={product}
          features={features}
          onAddToCart={handleAddToCart}
        >
          {/* children props - 在按钮上方添加额外内容 */}
          <div className="product-promotion">
            <span className="promotion-tag">限时优惠</span>
          </div>
        </ProductCard>
      </section>
      
      {/* 4. 使用 Layout 组件 */}
      <section className="section">
        <h2>布局示例</h2>
        <Layout
          header={<h1>网站头部</h1>}
          sidebar={
            <nav>
              <ul>
                <li>首页</li>
                <li>产品</li>
                <li>关于</li>
              </ul>
            </nav>
          }
          footer={<p>© 2025 React 学习网站</p>}
        >
          {/* 主要内容 */}
          <div className="main-content">
            <h2>主要内容区域</h2>
            <p>这里是主要内容,使用 children props 传递。</p>
          </div>
        </Layout>
      </section>
      
      {/* 5. 使用 InputField 组件 */}
      <section className="section">
        <h2>表单示例</h2>
        <div className="form-container">
          <InputField
            label="用户名"
            value={username}
            onChange={handleUsernameChange}
            placeholder="请输入用户名"
            error={usernameError}
            required={true}
          />
          
          <InputField
            label="邮箱"
            value={email}
            onChange={handleEmailChange}
            type="email"
            placeholder="请输入邮箱"
            error={emailError}
            required={true}
          />
          
          <Button
            text="提交表单"
            onClick={handleSubmit}
            variant="success"
            disabled={!!usernameError || !!emailError || !username || !email}
          />
        </div>
      </section>
      
      {/* 6. 购物车信息 */}
      <section className="section">
        <h2>购物车 ({cartItems.length} 件商品)</h2>
        {cartItems.length === 0 ? (
          <p>购物车为空</p>
        ) : (
          <ul>
            {cartItems.map((item, index) => (
              <li key={index}>{item.name} - ¥{item.price}</li>
            ))}
          </ul>
        )}
      </section>
    </div>
  );
}

// 导出所有组件
export {
  UserInfo,
  Button,
  ProductCard,
  Layout,
  InputField,
  App
};

export default App;

四、CSS 样式(可选,用于美化界面)

/* 基础样式 */
.app {
  max-width: 1200px;
  margin: 0 auto;
  padding: 20px;
  font-family: Arial, sans-serif;
}

.section {
  margin-bottom: 40px;
  padding: 20px;
  border: 1px solid #eee;
  border-radius: 8px;
}

/* 用户信息样式 */
.user-info {
  display: flex;
  align-items: center;
  gap: 20px;
  padding: 15px;
  border: 1px solid #ddd;
  border-radius: 8px;
}

.avatar {
  width: 50px;
  height: 50px;
  border-radius: 50%;
  object-fit: cover;
}

/* 按钮样式 */
.btn {
  padding: 10px 20px;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  font-size: 14px;
  margin-right: 10px;
  transition: all 0.3s ease;
}

.btn:hover {
  opacity: 0.9;
  transform: translateY(-1px);
}

.btn-primary { background-color: #007bff; color: white; }
.btn-secondary { background-color: #6c757d; color: white; }
.btn-danger { background-color: #dc3545; color: white; }
.btn-success { background-color: #28a745; color: white; }

.btn-small { padding: 5px 10px; font-size: 12px; }
.btn-medium { padding: 10px 20px; font-size: 14px; }
.btn-large { padding: 15px 30px; font-size: 16px; }

.btn-fullwidth { width: 100%; }

.btn:disabled {
  opacity: 0.6;
  cursor: not-allowed;
  transform: none;
}

.button-group {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
}

/* 产品卡片样式 */
.product-card {
  border: 1px solid #ddd;
  border-radius: 8px;
  overflow: hidden;
  max-width: 350px;
}

.product-image {
  width: 100%;
  height: 200px;
  object-fit: cover;
}

.product-info {
  padding: 20px;
}

.product-price {
  font-size: 24px;
  font-weight: bold;
  color: #e74c3c;
}

.product-features {
  margin: 15px 0;
}

.product-features h4 {
  margin-bottom: 10px;
  font-size: 16px;
}

.product-features ul {
  padding-left: 20px;
}

.product-promotion {
  margin-bottom: 15px;
}

.promotion-tag {
  background-color: #ff6b6b;
  color: white;
  padding: 5px 10px;
  border-radius: 4px;
  font-size: 12px;
}

/* 布局样式 */
.layout {
  border: 2px solid #333;
  border-radius: 8px;
  overflow: hidden;
}

.layout-header {
  background-color: #333;
  color: white;
  padding: 20px;
  text-align: center;
}

.layout-body {
  display: flex;
}

.layout-sidebar {
  width: 200px;
  background-color: #f8f9fa;
  padding: 20px;
  border-right: 1px solid #ddd;
}

.layout-sidebar ul {
  list-style: none;
  padding: 0;
}

.layout-sidebar li {
  padding: 10px 0;
  cursor: pointer;
}

.layout-sidebar li:hover {
  color: #007bff;
}

.layout-main {
  flex: 1;
  padding: 20px;
  background-color: #fff;
}

.layout-footer {
  background-color: #333;
  color: white;
  padding: 20px;
  text-align: center;
}

/* 表单样式 */
.form-container {
  max-width: 400px;
}

.input-field {
  margin-bottom: 20px;
}

.input-field label {
  display: block;
  margin-bottom: 5px;
  font-weight: bold;
}

.required {
  color: #e74c3c;
  margin-left: 5px;
}

.form-input {
  width: 100%;
  padding: 10px;
  border: 2px solid #ddd;
  border-radius: 4px;
  font-size: 16px;
}

.form-input:focus {
  outline: none;
  border-color: #007bff;
}

.has-error .form-input {
  border-color: #e74c3c;
}

.error-message {
  color: #e74c3c;
  font-size: 14px;
  margin-top: 5px;
  display: block;
}

五、关键要点总结

  1. Props 是只读的 - 组件内部不能修改 props
  2. 解构赋值 - 推荐使用 function Component({ prop1, prop2 }) 语法
  3. 默认值 - 使用 defaultProps 或参数默认值
  4. 类型检查 - 生产环境中建议使用 PropTypes 或 TypeScript
  5. Children Props - 特殊 prop,用于传递子元素
  6. Spread Operator - 方便批量传递 props
  7. 函数作为 Props - 用于事件处理和回调

这个完整的示例涵盖了 React 中 props 的所有主要用法,通过实际案例帮助你深入理解 props 的各种应用场景。


网站公告

今日签到

点亮在社区的每一天
去签到