components.jsx
文件信息
- 📄 原文件:
02_components.jsx - 🔤 语言:jsx
React 组件基础 React 组件是构建 UI 的基本单元。 组件可以是函数组件或类组件,现代 React 推荐使用函数组件。
完整代码
jsx
/**
* ============================================================
* React 组件基础
* ============================================================
* React 组件是构建 UI 的基本单元。
* 组件可以是函数组件或类组件,现代 React 推荐使用函数组件。
* ============================================================
*/
import React, { Component } from 'react';
// ============================================================
// 1. 函数组件
// ============================================================
/**
* 【函数组件】
*
* 函数组件是最简单的组件形式:
* - 接收 props 作为参数
* - 返回 JSX(描述 UI)
* - 没有 this 关键字
* - 配合 Hooks 可以使用状态和生命周期
*
* 【命名规则】
* - 组件名必须以大写字母开头
* - 使用 PascalCase 命名(如 UserProfile)
* - 文件名通常与组件名一致
*/
// --- 最简单的函数组件 ---
// 不接收任何参数,返回固定的 JSX
function Welcome() {
return <h1>欢迎来到 React 世界!</h1>;
}
// --- 箭头函数组件 ---
// 使用箭头函数语法,更简洁
const Greeting = () => {
return <p>你好!</p>;
};
// --- 带隐式返回的箭头函数 ---
// 当只有一个表达式时,可以省略 return 和花括号
const SimpleButton = () => <button>点击我</button>;
// ============================================================
// 2. Props(属性)
// ============================================================
/**
* 【Props 是什么】
*
* Props(properties)是组件的输入:
* - 从父组件传递给子组件
* - 在组件内部是只读的(不能修改)
* - 可以是任意类型:字符串、数字、对象、数组、函数等
*
* 【Props 的特点】
* - 单向数据流:数据从父组件流向子组件
* - 不可变:组件不能修改自己的 props
* - 声明式:通过 props 声明组件需要什么数据
*/
// --- 接收 Props ---
// props 是一个对象,包含所有传递给组件的属性
function UserGreeting(props) {
// props = { name: "Alice", age: 25 }
return (
<div>
<h2>你好, {props.name}!</h2>
<p>你今年 {props.age} 岁</p>
</div>
);
}
// 使用方式:
// <UserGreeting name="Alice" age={25} />
// --- 解构 Props ---
// 推荐使用解构语法,代码更清晰
function UserCard({ name, email, avatar }) {
return (
<div className="user-card">
<img src={avatar} alt={name} />
<h3>{name}</h3>
<p>{email}</p>
</div>
);
}
// 使用方式:
// <UserCard
// name="Alice"
// email="alice@example.com"
// avatar="/images/alice.jpg"
// />
// --- 默认 Props ---
// 为 props 设置默认值,当父组件没有传递时使用
function Button({ text = '按钮', type = 'primary', disabled = false }) {
return (
<button
className={`btn btn-${type}`}
disabled={disabled}
>
{text}
</button>
);
}
// 使用方式:
// <Button /> // 使用全部默认值
// <Button text="提交" /> // 只覆盖 text
// <Button text="删除" type="danger" /> // 覆盖多个
// --- 展开 Props ---
// 使用展开运算符传递所有属性
function Input(props) {
// 接收所有属性并传递给 input 元素
return <input className="custom-input" {...props} />;
}
// 使用方式:
// <Input type="text" placeholder="请输入" maxLength={100} />
// ============================================================
// 3. children Props
// ============================================================
/**
* 【children 是什么】
*
* children 是一个特殊的 prop:
* - 表示组件的子元素
* - 可以是文本、元素、组件或数组
* - 用于创建容器组件或布局组件
*/
// --- 基本 children 用法 ---
function Card({ title, children }) {
return (
<div className="card">
<div className="card-header">
<h3>{title}</h3>
</div>
<div className="card-body">
{/* children 会渲染在这里 */}
{children}
</div>
</div>
);
}
// 使用方式:
// <Card title="用户信息">
// <p>姓名: Alice</p>
// <p>邮箱: alice@example.com</p>
// </Card>
// --- 容器组件 ---
// 用于包装其他组件,提供通用功能
function Container({ maxWidth = '1200px', children }) {
const style = {
maxWidth,
margin: '0 auto',
padding: '0 20px',
};
return <div style={style}>{children}</div>;
}
// --- 布局组件 ---
// 用于页面布局
function Layout({ header, sidebar, children, footer }) {
return (
<div className="layout">
<header className="layout-header">{header}</header>
<div className="layout-content">
<aside className="layout-sidebar">{sidebar}</aside>
<main className="layout-main">{children}</main>
</div>
<footer className="layout-footer">{footer}</footer>
</div>
);
}
// 使用方式:
// <Layout
// header={<Navbar />}
// sidebar={<Menu />}
// footer={<Footer />}
// >
// <ArticleContent />
// </Layout>
// ============================================================
// 4. Props 类型和验证
// ============================================================
/**
* 【PropTypes】
*
* PropTypes 用于在开发环境中验证 props 类型:
* - 帮助捕获错误
* - 作为组件文档
* - 只在开发环境运行,不影响生产性能
*
* 【TypeScript 替代】
* 在 TypeScript 项目中,通常使用接口定义 props 类型
*/
import PropTypes from 'prop-types';
// --- PropTypes 验证 ---
function UserProfile({ name, age, email, isAdmin, tags, onUpdate }) {
return (
<div>
<h2>{name}</h2>
<p>年龄: {age}</p>
<p>邮箱: {email}</p>
{isAdmin && <span className="badge">管理员</span>}
<div>
标签: {tags.join(', ')}
</div>
<button onClick={onUpdate}>更新</button>
</div>
);
}
// 定义 props 类型
UserProfile.propTypes = {
// 必需的字符串
name: PropTypes.string.isRequired,
// 可选的数字
age: PropTypes.number,
// 可选的字符串
email: PropTypes.string,
// 可选的布尔值
isAdmin: PropTypes.bool,
// 字符串数组
tags: PropTypes.arrayOf(PropTypes.string),
// 必需的函数
onUpdate: PropTypes.func.isRequired,
};
// 定义默认值
UserProfile.defaultProps = {
age: 0,
email: '',
isAdmin: false,
tags: [],
};
// --- TypeScript 方式(推荐) ---
/*
interface UserProfileProps {
name: string;
age?: number;
email?: string;
isAdmin?: boolean;
tags?: string[];
onUpdate: () => void;
}
function UserProfile({
name,
age = 0,
email = '',
isAdmin = false,
tags = [],
onUpdate,
}: UserProfileProps) {
// ...
}
*/
// ============================================================
// 5. 组件组合
// ============================================================
/**
* 【组件组合】
*
* React 推荐使用组合而非继承:
* - 小组件组合成大组件
* - 通过 props 和 children 实现灵活性
* - 遵循单一职责原则
*/
// --- 小组件 ---
function Avatar({ src, alt, size = 'medium' }) {
const sizes = {
small: '32px',
medium: '48px',
large: '64px',
};
return (
<img
src={src}
alt={alt}
style={{
width: sizes[size],
height: sizes[size],
borderRadius: '50%',
}}
/>
);
}
function UserName({ name, isOnline }) {
return (
<span className="username">
{name}
{isOnline && <span className="online-dot">●</span>}
</span>
);
}
// --- 组合成复杂组件 ---
function UserInfo({ user }) {
return (
<div className="user-info">
<Avatar
src={user.avatar}
alt={user.name}
size="medium"
/>
<div className="user-details">
<UserName
name={user.name}
isOnline={user.isOnline}
/>
<span className="user-email">{user.email}</span>
</div>
</div>
);
}
// --- 特化组件 ---
// 通过预设 props 创建特化版本
function PrimaryButton(props) {
return <Button {...props} type="primary" />;
}
function DangerButton(props) {
return <Button {...props} type="danger" />;
}
function IconButton({ icon, children, ...props }) {
return (
<Button {...props}>
<span className="icon">{icon}</span>
{children}
</Button>
);
}
// ============================================================
// 6. 条件渲染组件
// ============================================================
/**
* 【条件渲染模式】
*
* 根据条件决定渲染哪个组件或元素
*/
// --- 登录状态组件 ---
function LoginButton({ onLogin }) {
return <button onClick={onLogin}>登录</button>;
}
function LogoutButton({ onLogout }) {
return <button onClick={onLogout}>退出</button>;
}
function AuthButton({ isLoggedIn, onLogin, onLogout }) {
// 根据登录状态返回不同组件
if (isLoggedIn) {
return <LogoutButton onLogout={onLogout} />;
}
return <LoginButton onLogin={onLogin} />;
}
// --- 加载状态组件 ---
function LoadingSpinner() {
return <div className="spinner">加载中...</div>;
}
function ErrorMessage({ message }) {
return <div className="error">{message}</div>;
}
function DataDisplay({ data }) {
return <div className="data">{JSON.stringify(data)}</div>;
}
function AsyncContent({ loading, error, data }) {
// 优先级:错误 > 加载 > 数据
if (error) {
return <ErrorMessage message={error} />;
}
if (loading) {
return <LoadingSpinner />;
}
if (data) {
return <DataDisplay data={data} />;
}
return null;
}
// ============================================================
// 7. 事件处理
// ============================================================
/**
* 【事件处理】
*
* React 事件处理的特点:
* - 事件名使用 camelCase(onClick 而非 onclick)
* - 传递函数而非字符串
* - 事件对象是合成事件(SyntheticEvent)
* - 需要显式调用 preventDefault() 阻止默认行为
*/
// --- 基本事件处理 ---
function ClickCounter() {
// 事件处理函数
const handleClick = () => {
console.log('按钮被点击');
};
// 带事件对象的处理函数
const handleClickWithEvent = (event) => {
console.log('点击位置:', event.clientX, event.clientY);
console.log('目标元素:', event.target);
};
return (
<div>
{/* 绑定事件处理函数 */}
<button onClick={handleClick}>点击我</button>
{/* 带事件对象 */}
<button onClick={handleClickWithEvent}>查看事件</button>
{/* 内联函数(简单逻辑时使用) */}
<button onClick={() => console.log('内联点击')}>
内联处理
</button>
</div>
);
}
// --- 传递参数给事件处理函数 ---
function ItemList() {
const items = ['苹果', '香蕉', '橙子'];
// 方式1: 使用箭头函数包装
const handleItemClick = (item, index) => {
console.log(`点击了第 ${index + 1} 个: ${item}`);
};
// 方式2: 使用柯里化
const createClickHandler = (item) => () => {
console.log(`点击了: ${item}`);
};
return (
<ul>
{items.map((item, index) => (
<li key={index}>
{/* 方式1: 箭头函数 */}
<button onClick={() => handleItemClick(item, index)}>
{item}
</button>
{/* 方式2: 柯里化 */}
<button onClick={createClickHandler(item)}>
{item} (柯里化)
</button>
</li>
))}
</ul>
);
}
// --- 表单事件 ---
function FormExample() {
const handleSubmit = (event) => {
// 阻止表单默认提交行为
event.preventDefault();
console.log('表单提交');
};
const handleInputChange = (event) => {
// 获取输入值
const value = event.target.value;
console.log('输入值:', value);
};
const handleKeyDown = (event) => {
// 检测按键
if (event.key === 'Enter') {
console.log('按下回车键');
}
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
onChange={handleInputChange}
onKeyDown={handleKeyDown}
placeholder="输入内容..."
/>
<button type="submit">提交</button>
</form>
);
}
// ============================================================
// 8. 类组件(了解即可)
// ============================================================
/**
* 【类组件】
*
* 类组件是 React 早期的组件写法:
* - 继承 React.Component
* - 使用 this.props 访问属性
* - 使用 this.state 管理状态
* - 有生命周期方法
*
* 【现代 React】
* - 推荐使用函数组件 + Hooks
* - 类组件仍然可用,但不推荐新代码使用
* - 了解类组件有助于维护旧代码
*/
class ClassComponent extends Component {
// 构造函数
constructor(props) {
super(props); // 必须调用 super(props)
// 初始化状态
this.state = {
count: 0,
};
// 绑定方法(或使用箭头函数)
this.handleClick = this.handleClick.bind(this);
}
// 事件处理方法
handleClick() {
// 使用 this.setState 更新状态
this.setState({ count: this.state.count + 1 });
}
// 箭头函数方法(自动绑定 this)
handleReset = () => {
this.setState({ count: 0 });
};
// 生命周期方法
componentDidMount() {
console.log('组件已挂载');
}
componentDidUpdate(prevProps, prevState) {
if (prevState.count !== this.state.count) {
console.log('count 已更新:', this.state.count);
}
}
componentWillUnmount() {
console.log('组件将卸载');
}
// 渲染方法(必需)
render() {
return (
<div>
<h2>{this.props.title}</h2>
<p>计数: {this.state.count}</p>
<button onClick={this.handleClick}>增加</button>
<button onClick={this.handleReset}>重置</button>
</div>
);
}
}
// ============================================================
// 9. 组件设计最佳实践
// ============================================================
/**
* 【组件设计原则】
*
* 1. 单一职责
* - 一个组件只做一件事
* - 如果组件太复杂,拆分成多个小组件
*
* 2. 可复用性
* - 通过 props 控制行为和外观
* - 避免硬编码
*
* 3. 可组合性
* - 小组件组合成大组件
* - 使用 children 实现灵活布局
*
* 4. 可测试性
* - 纯组件(相同 props 产生相同输出)
* - 逻辑和 UI 分离
*/
// --- 好的组件设计示例 ---
// 通用列表组件
function List({ items, renderItem, emptyMessage = '暂无数据' }) {
if (items.length === 0) {
return <p className="empty">{emptyMessage}</p>;
}
return (
<ul className="list">
{items.map((item, index) => (
<li key={item.id || index} className="list-item">
{renderItem(item)}
</li>
))}
</ul>
);
}
// 使用方式:
// <List
// items={users}
// renderItem={(user) => <UserCard user={user} />}
// emptyMessage="没有用户"
// />
// 通用模态框组件
function Modal({ isOpen, onClose, title, children, footer }) {
if (!isOpen) return null;
return (
<div className="modal-overlay" onClick={onClose}>
<div
className="modal-content"
onClick={(e) => e.stopPropagation()}
>
<div className="modal-header">
<h2>{title}</h2>
<button
className="modal-close"
onClick={onClose}
aria-label="关闭"
>
×
</button>
</div>
<div className="modal-body">{children}</div>
{footer && (
<div className="modal-footer">{footer}</div>
)}
</div>
</div>
);
}
// 使用方式:
// <Modal
// isOpen={showModal}
// onClose={() => setShowModal(false)}
// title="确认删除"
// footer={
// <>
// <Button onClick={handleCancel}>取消</Button>
// <Button type="danger" onClick={handleDelete}>删除</Button>
// </>
// }
// >
// <p>确定要删除这条记录吗?</p>
// </Modal>
// ============================================================
// 导出示例组件
// ============================================================
export {
Welcome,
Greeting,
SimpleButton,
UserGreeting,
UserCard,
Button,
Card,
Container,
Layout,
UserProfile,
Avatar,
UserName,
UserInfo,
AuthButton,
AsyncContent,
ClickCounter,
ItemList,
FormExample,
ClassComponent,
List,
Modal,
};
export default function ComponentsTutorial() {
const sampleUser = {
name: 'Alice',
email: 'alice@example.com',
avatar: '/default-avatar.png',
isOnline: true,
};
return (
<div className="tutorial">
<h1>React 组件教程</h1>
<Welcome />
<UserInfo user={sampleUser} />
<Card title="卡片示例">
<p>这是卡片内容</p>
</Card>
<ClickCounter />
</div>
);
}
💬 讨论
使用 GitHub 账号登录后即可参与讨论