React学习教程,从入门到精通, React 属性(Props)语法知识点与案例详解(14)

React 属性(Props)语法知识点与案例详解

一、Props 基础概念

Props(Properties 的缩写)是 React 组件接收外部数据的主要方式。它们是从父组件传递给子组件的只读数据,用于配置组件的行为和显示内容。

二、Props 核心语法知识点

1. Props 基本传递

jsx 复制代码
// 父组件传递 props
<ChildComponent name="张三" age={25} />

// 子组件接收 props
function ChildComponent(props) {
  return <div>{props.name} - {props.age}岁</div>
}

2. Props 解构赋值(推荐)

jsx 复制代码
// 函数组件中解构 props
function ChildComponent({ name, age, children }) {
  return (
    <div>
      <p>姓名: {name}</p>
      <p>年龄: {age}</p>
      {children}
    </div>
  )
}

// 或者在函数体内解构
function ChildComponent(props) {
  const { name, age } = props;
  return <div>{name} - {age}</div>
}

3. Props 默认值

jsx 复制代码
// 函数组件设置默认 props
function UserProfile({ name = "匿名用户", age = 0, email = "未提供" }) {
  return (
    <div>
      <p>姓名: {name}</p>
      <p>年龄: {age}</p>
      <p>邮箱: {email}</p>
    </div>
  )
}

// 使用 defaultProps(适用于类组件或函数组件)
UserProfile.defaultProps = {
  name: "匿名用户",
  age: 0,
  email: "未提供邮箱"
};

4. Props 类型检查(PropTypes)

jsx 复制代码
import PropTypes from 'prop-types';

function UserCard({ name, age, isActive, hobbies, onButtonClick }) {
  return (
    <div>
      <h3>{name} ({age}岁)</h3>
      <p>状态: {isActive ? '活跃' : '非活跃'}</p>
      <button onClick={onButtonClick}>点击我</button>
    </div>
  )
}

// 定义 prop 类型
UserCard.propTypes = {
  name: PropTypes.string.isRequired,    // 必填字符串
  age: PropTypes.number,                // 数字
  isActive: PropTypes.bool,             // 布尔值
  hobbies: PropTypes.arrayOf(PropTypes.string), // 字符串数组
  onButtonClick: PropTypes.func         // 函数
};

// 设置默认值
UserCard.defaultProps = {
  age: 18,
  isActive: true,
  hobbies: [],
  onButtonClick: () => console.log('按钮被点击')
};

5. 传递对象作为 Props

jsx 复制代码
// 传递对象
const userInfo = {
  name: "李四",
  age: 30,
  email: "lisi@example.com"
};

<UserProfile user={userInfo} />

// 子组件接收
function UserProfile({ user }) {
  return (
    <div>
      <p>姓名: {user.name}</p>
      <p>年龄: {user.age}</p>
      <p>邮箱: {user.email}</p>
    </div>
  )
}

// 或者使用展开运算符
<UserProfile {...userInfo} />

// 子组件
function UserProfile({ name, age, email }) {
  return (
    <div>
      <p>姓名: {name}</p>
      <p>年龄: {age}</p>
      <p>邮箱: {email}</p>
    </div>
  )
}

6. Children Props

jsx 复制代码
// 父组件传递子元素
<Container>
  <h1>这是标题</h1>
  <p>这是段落内容</p>
  <button>按钮</button>
</Container>

// 子组件接收 children
function Container({ children, style }) {
  return (
    <div style={{ padding: '20px', border: '1px solid #ccc', ...style }}>
      {children} {/* 渲染所有子元素 */}
    </div>
  )
}

7. 函数作为 Props(回调函数)

jsx 复制代码
// 父组件
function ParentComponent() {
  const handleUserClick = (userId, userName) => {
    console.log(`用户 ${userName} (ID: ${userId}) 被点击`);
    alert(`你好,${userName}!`);
  };

  return (
    <UserList onItemClick={handleUserClick} />
  )
}

// 子组件
function UserList({ onItemClick }) {
  const users = [
    { id: 1, name: "张三" },
    { id: 2, name: "李四" },
    { id: 3, name: "王五" }
  ];

  return (
    <div>
      {users.map(user => (
        <div 
          key={user.id} 
          onClick={() => onItemClick(user.id, user.name)}
          style={{ cursor: 'pointer', padding: '10px', margin: '5px', border: '1px solid #ddd' }}
        >
          {user.name}
        </div>
      ))}
    </div>
  )
}

8. 数组作为 Props

jsx 复制代码
function TodoList({ todos }) {
  return (
    <ul>
      {todos.map((todo, index) => (
        <li key={index}>
          <input type="checkbox" defaultChecked={todo.completed} />
          <span style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}>
            {todo.text}
          </span>
        </li>
      ))}
    </ul>
  )
}

// 使用组件
const todoItems = [
  { text: "学习 React", completed: false },
  { text: "完成项目", completed: true },
  { text: "阅读文档", completed: false }
];

<TodoList todos={todoItems} />

9. 条件 Props

jsx 复制代码
function ConditionalComponent({ showTitle, title, content }) {
  return (
    <div>
      {/* 条件渲染 title */}
      {showTitle && <h2>{title}</h2>}
      <p>{content}</p>
    </div>
  )
}

// 使用
<ConditionalComponent 
  showTitle={true} 
  title="条件标题" 
  content="这是内容"
/>

10. Spread Operator 传递 Props

jsx 复制代码
function UserProfile({ name, age, city, country, ...otherProps }) {
  return (
    <div {...otherProps}> {/* 将剩余的 props 传递给 div */}
      <h3>{name}</h3>
      <p>年龄: {age}</p>
      <p>城市: {city}, 国家: {country}</p>
    </div>
  )
}

// 使用
const userProps = {
  name: "赵六",
  age: 28,
  city: "北京",
  country: "中国",
  style: { backgroundColor: '#f0f0f0', padding: '20px' },
  className: "user-profile"
};

<UserProfile {...userProps} />

三、完整案例:用户管理系统

jsx 复制代码
import React from 'react';
import PropTypes from 'prop-types';

// 1. 用户卡片组件
function UserCard({ 
  user, 
  onEdit, 
  onDelete, 
  isSelected = false,
  className = ""
}) {
  // 解构用户对象
  const { id, name, email, age, avatar } = user;
  
  return (
    <div 
      className={`user-card ${isSelected ? 'selected' : ''} ${className}`}
      style={{
        border: '1px solid #ddd',
        borderRadius: '8px',
        padding: '15px',
        margin: '10px 0',
        backgroundColor: isSelected ? '#e3f2fd' : '#fff',
        boxShadow: '0 2px 4px rgba(0,0,0,0.1)',
        cursor: 'pointer'
      }}
    >
      <div style={{ display: 'flex', alignItems: 'center' }}>
        {/* 用户头像 */}
        <img 
          src={avatar || 'https://via.placeholder.com/50'} 
          alt={name}
          style={{ 
            width: '50px', 
            height: '50px', 
            borderRadius: '50%', 
            marginRight: '15px' 
          }} 
        />
        
        {/* 用户信息 */}
        <div style={{ flex: 1 }}>
          <h3 style={{ margin: '0 0 5px 0', color: '#333' }}>{name}</h3>
          <p style={{ margin: '0 0 5px 0', fontSize: '14px', color: '#666' }}>
            {email}
          </p>
          <p style={{ margin: '0', fontSize: '14px', color: '#666' }}>
            年龄: {age}岁
          </p>
        </div>
        
        {/* 操作按钮 */}
        <div>
          <button 
            onClick={(e) => {
              e.stopPropagation(); // 阻止事件冒泡
              onEdit(id);
            }}
            style={{
              marginRight: '5px',
              padding: '5px 10px',
              backgroundColor: '#007bff',
              color: 'white',
              border: 'none',
              borderRadius: '4px',
              cursor: 'pointer'
            }}
          >
            编辑
          </button>
          <button 
            onClick={(e) => {
              e.stopPropagation();
              onDelete(id);
            }}
            style={{
              padding: '5px 10px',
              backgroundColor: '#dc3545',
              color: 'white',
              border: 'none',
              borderRadius: '4px',
              cursor: 'pointer'
            }}
          >
            删除
          </button>
        </div>
      </div>
    </div>
  );
}

// 定义 UserCard 的 prop 类型
UserCard.propTypes = {
  user: PropTypes.shape({
    id: PropTypes.number.isRequired,
    name: PropTypes.string.isRequired,
    email: PropTypes.string.isRequired,
    age: PropTypes.number.isRequired,
    avatar: PropTypes.string
  }).isRequired,
  onEdit: PropTypes.func.isRequired,
  onDelete: PropTypes.func.isRequired,
  isSelected: PropTypes.bool,
  className: PropTypes.string
};

// 2. 用户列表组件
function UserList({ 
  users, 
  onUserEdit, 
  onUserDelete, 
  selectedUserId, 
  onUserSelect 
}) {
  return (
    <div>
      <h2>用户列表 ({users.length} 位用户)</h2>
      {users.length === 0 ? (
        <p style={{ textAlign: 'center', color: '#666' }}>暂无用户数据</p>
      ) : (
        users.map(user => (
          <UserCard
            key={user.id}
            user={user}
            onEdit={onUserEdit}
            onDelete={onUserDelete}
            isSelected={selectedUserId === user.id}
            onClick={() => onUserSelect(user.id)}
          />
        ))
      )}
    </div>
  );
}

// 定义 UserList 的 prop 类型
UserList.propTypes = {
  users: PropTypes.arrayOf(
    PropTypes.shape({
      id: PropTypes.number.isRequired,
      name: PropTypes.string.isRequired,
      email: PropTypes.string.isRequired,
      age: PropTypes.number.isRequired,
      avatar: PropTypes.string
    })
  ).isRequired,
  onUserEdit: PropTypes.func.isRequired,
  onUserDelete: PropTypes.func.isRequired,
  selectedUserId: PropTypes.number,
  onUserSelect: PropTypes.func.isRequired
};

// 3. 用户统计组件
function UserStats({ users, filterByAge }) {
  // 计算统计数据
  const totalUsers = users.length;
  const averageAge = totalUsers > 0 
    ? (users.reduce((sum, user) => sum + user.age, 0) / totalUsers).toFixed(1) 
    : 0;
  const activeUsers = users.filter(user => user.age >= filterByAge).length;
  
  return (
    <div style={{
      backgroundColor: '#f8f9fa',
      padding: '15px',
      borderRadius: '8px',
      marginBottom: '20px',
      border: '1px solid #e9ecef'
    }}>
      <h3>用户统计</h3>
      <p>总用户数: <strong>{totalUsers}</strong></p>
      <p>平均年龄: <strong>{averageAge}</strong> 岁</p>
      <p>年龄 ≥ {filterByAge} 岁的用户: <strong>{activeUsers}</strong> 位</p>
    </div>
  );
}

// 定义 UserStats 的 prop 类型
UserStats.propTypes = {
  users: PropTypes.array.isRequired,
  filterByAge: PropTypes.number
};

UserStats.defaultProps = {
  filterByAge: 18
};

// 4. 主应用组件
function App() {
  // 模拟用户数据
  const [users, setUsers] = React.useState([
    {
      id: 1,
      name: "张三",
      email: "zhangsan@example.com",
      age: 25,
      avatar: "https://via.placeholder.com/50/007bff/ffffff?text=ZS"
    },
    {
      id: 2,
      name: "李四",
      email: "lisi@example.com",
      age: 32,
      avatar: "https://via.placeholder.com/50/28a745/ffffff?text=LS"
    },
    {
      id: 3,
      name: "王五",
      email: "wangwu@example.com",
      age: 28,
      avatar: "https://via.placeholder.com/50/dc3545/ffffff?text=WW"
    }
  ]);
  
  // 选中的用户ID
  const [selectedUserId, setSelectedUserId] = React.useState(null);
  
  // 编辑用户
  const handleEditUser = (userId) => {
    alert(`编辑用户 ID: ${userId}`);
    // 这里可以打开编辑模态框或跳转到编辑页面
  };
  
  // 删除用户
  const handleDeleteUser = (userId) => {
    if (window.confirm('确定要删除这个用户吗?')) {
      setUsers(users.filter(user => user.id !== userId));
      if (selectedUserId === userId) {
        setSelectedUserId(null);
      }
    }
  };
  
  // 选择用户
  const handleSelectUser = (userId) => {
    setSelectedUserId(userId);
  };
  
  // 添加示例用户
  const handleAddSampleUser = () => {
    const newId = users.length > 0 ? Math.max(...users.map(u => u.id)) + 1 : 1;
    const newUser = {
      id: newId,
      name: `用户${newId}`,
      email: `user${newId}@example.com`,
      age: Math.floor(Math.random() * 30) + 18, // 18-47岁随机年龄
      avatar: `https://via.placeholder.com/50/${Math.floor(Math.random()*16777215).toString(16)}/ffffff?text=U${newId}`
    };
    setUsers([...users, newUser]);
  };

  return (
    <div style={{ maxWidth: '800px', margin: '0 auto', padding: '20px' }}>
      <h1>React Props 详细案例 - 用户管理系统</h1>
      
      {/* 用户统计 */}
      <UserStats users={users} filterByAge={25} />
      
      {/* 添加用户按钮 */}
      <button 
        onClick={handleAddSampleUser}
        style={{
          marginBottom: '20px',
          padding: '10px 20px',
          backgroundColor: '#28a745',
          color: 'white',
          border: 'none',
          borderRadius: '4px',
          cursor: 'pointer',
          fontSize: '16px'
        }}
      >
        添加示例用户
      </button>
      
      {/* 用户列表 */}
      <UserList
        users={users}
        onUserEdit={handleEditUser}
        onUserDelete={handleDeleteUser}
        selectedUserId={selectedUserId}
        onUserSelect={handleSelectUser}
      />
      
      {/* 选中用户信息 */}
      {selectedUserId && (
        <div style={{
          marginTop: '20px',
          padding: '15px',
          backgroundColor: '#e3f2fd',
          border: '1px solid #2196f3',
          borderRadius: '8px'
        }}>
          <h3>当前选中的用户: {users.find(u => u.id === selectedUserId)?.name}</h3>
        </div>
      )}
    </div>
  );
}

// 导出组件
export default App;
export { UserCard, UserList, UserStats };

四、Props 使用注意事项

1. Props 是只读的

jsx 复制代码
// ❌ 错误:不能修改 props
function BadComponent(props) {
  props.name = "新名字"; // 不要这样做!
  return <div>{props.name}</div>;
}

// ✅ 正确:使用状态或创建新变量
function GoodComponent({ name }) {
  const [localName, setLocalName] = useState(name);
  return <div>{localName}</div>;
}

2. 避免传递过多 Props(Prop Drilling)

jsx 复制代码
// ❌ 不好的做法:层层传递 props
<GrandParent user={user}>
  <Parent user={user}>
    <Child user={user}>
      <GrandChild user={user} />
    </Child>
  </Parent>
</GrandParent>

// ✅ 更好的做法:使用 Context API 或状态管理库

3. 性能优化:避免不必要的重新渲染

jsx 复制代码
// 使用 React.memo 避免不必要的重新渲染
const MemoizedUserCard = React.memo(UserCard);

// 使用 useCallback 避免函数重新创建
function ParentComponent() {
  const handleEdit = useCallback((id) => {
    console.log('编辑用户', id);
  }, []); // 依赖数组为空,函数不会重新创建
  
  return <UserCard onEdit={handleEdit} />;
}

五、总结

Props 是 React 组件间通信的基础,掌握以下要点:

  1. 数据流向:单向数据流,从父组件到子组件
  2. 只读性:Props 是只读的,不能在子组件中修改
  3. 灵活性:可以传递任何类型的数据(字符串、数字、数组、对象、函数等)
  4. 类型安全:使用 PropTypes 进行类型检查
  5. 默认值:为 Props 设置合理的默认值
  6. 解构赋值:推荐使用解构赋值提高代码可读性
  7. Children:特殊 prop,用于传递子元素
  8. 性能考虑:避免不必要的重新渲染
相关推荐
luckys.one5 小时前
第9篇:Freqtrade量化交易之config.json 基础入门与初始化
javascript·数据库·python·mysql·算法·json·区块链
魔云连洲5 小时前
深入解析:Vue与React的异步批处理更新机制
前端·vue.js·react.js
mCell6 小时前
JavaScript 的多线程能力:Worker
前端·javascript·浏览器
weixin_437830947 小时前
使用冰狐智能辅助实现图形列表自动点击:OCR与HID技术详解
开发语言·javascript·ocr
超级无敌攻城狮7 小时前
3 分钟学会!波浪文字动画超详细教程,从 0 到 1 实现「思考中 / 加载中」高级效果
前端
茯苓gao8 小时前
STM32G4 速度环开环,电流环闭环 IF模式建模
笔记·stm32·单片机·嵌入式硬件·学习
excel8 小时前
用 TensorFlow.js Node 实现猫图像识别(教学版逐步分解)
前端
是誰萆微了承諾8 小时前
【golang学习笔记 gin 】1.2 redis 的使用
笔记·学习·golang
前端工作日常8 小时前
我学习到的Vue2.6的prop修饰符
vue.js