pin_drop当前位置:知识文库 ❯ 图文
框架集成:04 React组件与HTML - 详细教程与实战指南
目录
一、教程简介
组件是React的基石,每个React应用都是由组件构成的组件树。组件本质上是一个返回UI描述的函数(或类),它接收props作为输入,返回描述HTML结构的React元素。本教程将深入讲解函数组件与类组件、Props传递、Children组合模式、组件通信、高阶组件、Render Props以及常见的组件设计模式,帮助你掌握React组件化开发的精髓。
二、函数组件
函数组件是React推荐的组件编写方式,它是一个纯函数,接收props返回React元素:
代码示例
function UserCard({ name, email, role = 'user', avatar }) {
const roleBadge = {
admin: 'badge-admin',
user: 'badge-user',
guest: 'badge-guest'
};
const roleLabel = {
admin: '管理员',
user: '普通用户',
guest: '访客'
};
return (
<div className="user-card">
<img className="user-avatar" src={avatar} alt={name} />
<div className="user-info">
<h3>
{name}
<span className={`badge ${roleBadge[role]}`}>{roleLabel[role]}</span>
</h3>
<p>{email}</p>
</div>
</div>
);
}
// 带状态的函数组件(使用Hooks)
function StatsPanel() {
const [stats, setStats] = React.useState({
users: 1250,
orders: 386,
revenue: 52800
});
function refreshStats() {
setStats({
users: stats.users + Math.floor(Math.random() * 10),
orders: stats.orders + Math.floor(Math.random() * 5),
revenue: stats.revenue + Math.floor(Math.random() * 1000)
});
}
return (
<div>
<div className="stats-grid">
<div className="stat-card" style={{ background: 'linear-gradient(135deg, #667eea, #764ba2)' }}>
<div className="stat-value">{stats.users.toLocaleString()}</div>
<div className="stat-label">用户数</div>
</div>
<div className="stat-card" style={{ background: 'linear-gradient(135deg, #f093fb, #f5576c)' }}>
<div className="stat-value">{stats.orders.toLocaleString()}</div>
<div className="stat-label">订单数</div>
</div>
<div className="stat-card" style={{ background: 'linear-gradient(135deg, #4facfe, #00f2fe)' }}>
<div className="stat-value">¥{stats.revenue.toLocaleString()}</div>
<div className="stat-label">收入</div>
</div>
</div>
<button onClick={refreshStats}>刷新数据</button>
</div>
);
}三、类组件
类组件是React早期的组件编写方式,使用ES6 class语法:
代码示例
// 类组件:计时器
class Timer extends React.Component {
constructor(props) {
super(props);
this.state = {
seconds: 0,
isRunning: false
};
this.intervalId = null;
}
start = () => {
if (this.state.isRunning) return;
this.setState({ isRunning: true });
this.intervalId = setInterval(() => {
this.setState(prev => ({ seconds: prev.seconds + 1 }));
}, 1000);
};
pause = () => {
this.setState({ isRunning: false });
clearInterval(this.intervalId);
};
reset = () => {
this.setState({ seconds: 0, isRunning: false });
clearInterval(this.intervalId);
};
componentWillUnmount() {
clearInterval(this.intervalId);
}
formatTime(totalSeconds) {
const hours = Math.floor(totalSeconds / 3600);
const minutes = Math.floor((totalSeconds % 3600) / 60);
const seconds = totalSeconds % 60;
return `${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`;
}
render() {
return (
<div className="timer">
<div className="timer-value">{this.formatTime(this.state.seconds)}</div>
<div className="timer-controls">
{!this.state.isRunning ? (
<button className="btn-start" onClick={this.start}>开始</button>
) : (
<button className="btn-pause" onClick={this.pause}>暂停</button>
)}
<button className="btn-reset" onClick={this.reset}>重置</button>
</div>
</div>
);
}
}四、Props传递与Children组合
代码示例
// Card组件 - Children组合模式
function Card({ title, children, footer }) {
return (
<div className="card">
{title && <div className="card-header">{title}</div>}
<div className="card-body">{children}</div>
{footer && <div className="card-footer">{footer}</div>}
</div>
);
}
// Alert组件 - 不同类型的提示
function Alert({ type = 'info', icon, children }) {
const icons = { success: '✓', warning: '⚠', error: '✕', info: 'ℹ' };
return (
<div className={`alert alert-${type}`}>
<span className="alert-icon">{icon || icons[type]}</span>
<span>{children}</span>
</div>
);
}
// Layout组件 - 多插槽组合
function Layout({ sidebar, children, aside }) {
return (
<div className="layout">
<div className="layout-sidebar">{sidebar}</div>
<div className="layout-main">{children}</div>
<div className="layout-aside">{aside}</div>
</div>
);
}五、组件通信
代码示例
// 父子通信:通过props和回调函数
function ShoppingCart({ cart, onRemove, total }) {
return (
<div>
<h4>购物车</h4>
{cart.length === 0 ? (
<p style={{ color: '#999', textAlign: 'center', padding: '20px' }}>购物车为空</p>
) : (
<div>
{cart.map(item => (
<div key={item.id} className="cart-item">
<span>{item.name} x {item.count}</span>
<span>¥{(item.price * item.count).toFixed(2)}
<button onClick={() => onRemove(item.id)}>移除</button>
</span>
</div>
))}
<div className="total">合计:¥{total.toFixed(2)}</div>
</div>
)}
</div>
);
}
function ProductList({ products, onAdd }) {
return (
<div>
<h4>商品列表</h4>
<div className="product-list">
{products.map(product => (
<div key={product.id} className="product-item">
<div style={{ fontWeight: 'bold' }}>{product.name}</div>
<div style={{ color: '#e74c3c' }}>¥{product.price}</div>
<button onClick={() => onAdd(product)}>加入购物车</button>
</div>
))}
</div>
</div>
);
}
// 父组件管理状态,子组件通过回调通信
function ShopApp() {
const products = [
{ id: 1, name: 'React书籍', price: 79.9 },
{ id: 2, name: 'Vue书籍', price: 59.9 },
{ id: 3, name: 'Angular书籍', price: 69.9 },
{ id: 4, name: 'Svelte书籍', price: 49.9 }
];
const [cart, setCart] = React.useState([]);
function addToCart(product) {
setCart(prev => {
const existing = prev.find(item => item.id === product.id);
if (existing) {
return prev.map(item =>
item.id === product.id
? { ...item, count: item.count + 1 }
: item
);
}
return [...prev, { ...product, count: 1 }];
});
}
function removeFromCart(id) {
setCart(prev => prev.filter(item => item.id !== id));
}
const total = cart.reduce((sum, item) => sum + item.price * item.count, 0);
return (
<div>
<ProductList products={products} onAdd={addToCart} />
<ShoppingCart cart={cart} onRemove={removeFromCart} total={total} />
</div>
);
}六、高阶组件与Render Props
代码示例
// Render Props模式:Mouse追踪器
function Mouse({ render }) {
const [position, setPosition] = React.useState({ x: 0, y: 0 });
function handleMouseMove(e) {
const rect = e.currentTarget.getBoundingClientRect();
setPosition({
x: e.clientX - rect.left,
y: e.clientY - rect.top
});
}
return (
<div className="mouse-tracker" onMouseMove={handleMouseMove}>
{render(position)}
</div>
);
}
// 高阶组件模式:withToggle
function withToggle(WrappedComponent) {
return function ToggleHOC(props) {
const [isOn, setIsOn] = React.useState(false);
return (
<WrappedComponent
{...props}
isOn={isOn}
toggle={() => setIsOn(!isOn)}
/>
);
};
}
// 使用withToggle的组件
function SettingsPanel({ isOn, toggle, title }) {
return (
<div className="toggle-content">
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<h4 style={{ margin: 0 }}>{title}</h4>
<button className={isOn ? 'btn-success' : 'btn-primary'} onClick={toggle}>
{isOn ? '已开启' : '已关闭'}
</button>
</div>
{isOn && <p style={{ marginTop: '10px', color: '#666' }}>功能已启用,可以进行相关操作。</p>}
</div>
);
}
const EnhancedSettings = withToggle(SettingsPanel);七、组件设计模式
代码示例
// 模式1:Tabs组件(受控模式)
function Tabs({ activeTab, onTabChange, children }) {
return (
<div>
<div className="tabs">
{React.Children.map(children, child => (
<button
className={`tab ${activeTab === child.props.label ? 'active' : ''}`}
onClick={() => onTabChange(child.props.label)}
>
{child.props.label}
</button>
))}
</div>
<div className="tab-content">
{React.Children.map(children, child =>
child.props.label === activeTab ? child : null
)}
</div>
</div>
);
}
// 模式2:Accordion组件(非受控模式)
function Accordion({ items }) {
const [openIndex, setOpenIndex] = React.useState(-1);
return (
<div>
{items.map((item, index) => (
<div key={index} className="accordion-item">
<div className="accordion-header" onClick={() => setOpenIndex(openIndex === index ? -1 : index)}>
<span>{item.title}</span>
<span>{openIndex === index ? '−' : '+'}</span>
</div>
<div className={`accordion-body ${openIndex === index ? 'open' : ''}`}>
{item.content}
</div>
</div>
))}
</div>
);
}
// 模式3:Modal组件(条件渲染模式)
function Modal({ isOpen, onClose, title, children }) {
if (!isOpen) return null;
return (
<div className="modal-overlay" onClick={onClose}>
<div className="modal-content" onClick={e => e.stopPropagation()}>
<h3 className="modal-title">{title}</h3>
{children}
<div className="modal-actions">
<button className="btn-secondary" onClick={onClose}>取消</button>
<button className="btn-primary" onClick={onClose}>确认</button>
</div>
</div>
</div>
);
}八、浏览器兼容性
九、注意事项与最佳实践
-
优先使用函数组件:React官方推荐使用函数组件+Hooks,类组件仅用于维护旧代码
-
Props是只读的:组件绝不能修改自己的props,这是React单向数据流的基础
-
合理使用默认值:使用默认参数或defaultProps为props提供默认值
-
避免prop drilling:当props需要传递多层时,考虑使用Context或状态管理库
-
组件职责单一:每个组件只做一件事,复杂组件应拆分为更小的子组件
-
提取可复用逻辑:使用自定义Hook替代高阶组件和Render Props
十、代码规范示例
代码示例
// 不推荐:庞大的单体组件
function Dashboard() {
const [users, setUsers] = useState([]);
const [orders, setOrders] = useState([]);
const [stats, setStats] = useState({});
// 100行渲染逻辑...
}
// 推荐:拆分为小组件
function Dashboard() {
return (
<div>
<UserSummary />
<OrderList />
<StatsPanel />
</div>
);
}
// 不推荐:直接修改props
function BadComponent({ items }) {
items.push('new item'); // 不要修改props!
}
// 推荐:使用状态管理数据
function GoodComponent({ items }) {
const [localItems, setLocalItems] = useState(items);
function addItem() {
setLocalItems([...localItems, 'new item']);
}
}
// 不推荐:深层prop drilling
<GrandParent>
<Parent theme={theme}>
<Child theme={theme}>
<GrandChild theme={theme} /> {/* theme传了3层 */}
</Child>
</Parent>
</GrandParent>
// 推荐:使用Context
const ThemeContext = createContext();
<ThemeContext.Provider value={theme}>
<GrandParent>
<Parent>
<Child>
<GrandChild /> {/* 通过useContext(ThemeContext)获取 */}
</Child>
</Parent>
</GrandParent>
</ThemeContext.Provider>十一、常见问题与解决方案
常见问题
函数组件和类组件哪个性能更好?
函数组件略优,因为它不需要创建类实例,内存占用更少。但实际差异很小,不应作为选择的主要依据。选择函数组件的主要原因是Hooks更简洁、更易组合。
什么时候用受控组件,什么时候用非受控组件?
如果父组件需要知道和控制子组件的状态(如表单验证、联动),用受控组件。如果组件状态是内部实现细节(如手风琴展开/折叠),用非受控组件。
高阶组件和自定义Hook怎么选?
优先使用自定义Hook。Hook更简洁、没有嵌套地狱问题、更容易类型推导。高阶组件主要用于需要操作包裹组件props或渲染结构的场景。
children prop有什么限制?
children可以是任何类型:字符串、元素、数组、函数(Render Props)甚至undefined。但注意React.Children.map会过滤掉null和undefined。
如何调试组件的props和state?
使用React DevTools浏览器扩展,可以查看组件树、props、state和hooks。也可以在代码中使用console.log,但注意不要在render中产生副作用。
十二、总结
本教程深入讲解了React组件与HTML模板的关系。函数组件是React推荐的组件编写方式,通过Hooks管理状态和副作用。Props是组件间通信的基础,遵循单向数据流原则。Children组合模式提供了灵活的组件组合能力,让组件更具复用性。高阶组件和Render Props是旧的模式,现代React推荐使用自定义Hook来复用逻辑。常见的组件设计模式包括受控/非受控组件、Tabs、Accordion、Modal等,理解这些模式有助于构建可维护的组件库。下一教程将介绍React事件处理与HTML的关系。
本文涉及AI创作
内容由AI创作,请仔细甄别