第二章 JSX 语法详解
JSX(JavaScript XML)是 React 的核心语法扩展,它允许我们在 JavaScript 中编写类似 HTML 的代码,使得组件的结构更加直观和易于理解。
2.1 JSX 基础概念
2.1.1 JSX 的本质
JSX 编译过程:
graph TDA[JSX 代码] --> B[Babel 编译器]B --> C[React.createElement 调用]C --> D[Virtual DOM 对象]D --> E[真实 DOM]F[TypeScript] --> G[类型检查]G --> A
JSX 转换示例:
// JSX 代码
const element = (<div className="container"><h1>Hello World</h1><p>Current time: {new Date().toLocaleTimeString()}</p></div>
);// 编译后的 JavaScript 代码(React 17+)
const element = React.createElement('div',{ className: 'container' },React.createElement('h1', null, 'Hello World'),React.createElement('p', null, 'Current time: ', new Date().toLocaleTimeString())
);// React 17+ 自动转换(不需要手动导入 React)
import { jsx as _jsx } from 'react/jsx-runtime';
const element = _jsx('div', {className: 'container',children: [_jsx('h1', { children: 'Hello World' }),_jsx('p', { children: ['Current time: ', new Date().toLocaleTimeString()] })]
});
2.1.2 JSX 与 HTML 的区别
语法差异对比表:
| 特性 | HTML | JSX | 说明 |
|---|---|---|---|
| 属性命名 | class |
className |
避免与 JavaScript 关键字冲突 |
| 标签闭合 | <img>, <br> |
<img />, <br /> |
所有标签必须闭合 |
| 布尔属性 | disabled |
disabled={true} |
布尔值需显式指定 |
| 样式属性 | style="color: red" |
style={{ color: 'red' }} |
使用对象语法 |
| 注释 | <!-- comment --> |
{/* comment */} |
JavaScript 注释语法 |
| 自定义属性 | data-* |
data-* |
data 属性保持一致 |
| 事件命名 | onclick |
onClick |
驼峰命名法 |
具体示例对比:
<!-- HTML 代码 -->
<div class="container" style="margin: 0 auto; width: 100%"><img src="logo.png" alt="Logo" class="logo" /><button onclick="handleClick()" disabled>Submit</button><input type="text" placeholder="Enter name" required /><!-- This is a comment -->
</div>
// JSX 代码
<div className="container" style={{ margin: '0 auto', width: '100%' }}><img src="logo.png" alt="Logo" className="logo" /><button onClick={handleClick} disabled={true}>Submit</button><input type="text" placeholder="Enter name" required={true} />{/* This is a comment */}
</div>
2.1.3 JSX 的类型定义
TypeScript JSX 类型:
// JSX.Element 类型
const element: JSX.Element = <div>Hello</div>;// JSX.IntrinsicElements - 内置元素类型
declare global {namespace JSX {interface IntrinsicElements {div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>;span: React.DetailedHTMLProps<React.HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>;button: React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>;// ... 其他内置元素}}
}// 自定义组件类型
interface CustomComponentProps {title: string;count: number;onAction?: () => void;
}const CustomComponent: React.FC<CustomComponentProps> = ({ title, count, onAction }) => {return (<div><h2>{title}</h2><p>Count: {count}</p>{onAction && <button onClick={onAction}>Action</button>}</div>);
};// 使用自定义组件
const App = () => {return (<CustomComponent title="My App" count={42} onAction={() => console.log('Action triggered')}/>);
};
2.2 JSX 核心语法
2.2.1 元素与组件
函数组件定义:
// 基础函数组件
function Welcome(props) {return <h1>Hello, {props.name}</h1>;
}// 箭头函数组件
const Welcome = (props) => {return <h1>Hello, {props.name}</h1>;
};// 箭头函数组件(隐式返回)
const Welcome = (props) => <h1>Hello, {props.name}</h1>;// TypeScript 组件
interface WelcomeProps {name: string;age?: number;
}const Welcome: React.FC<WelcomeProps> = ({ name, age }) => (<h1>Hello, {name}!{age && ` Age: ${age}`}</h1>
);// 使用组件
function App() {return (<div><Welcome name="Alice" /><Welcome name="Bob" age={25} /></div>);
}
类组件定义:
import React, { Component } from 'react';class Welcome extends Component {constructor(props) {super(props);this.state = {message: 'Welcome to React!'};}render() {const { name } = this.props;const { message } = this.state;return (<div><h1>Hello, {name}</h1><p>{message}</p></div>);}
}// TypeScript 类组件
interface WelcomeProps {name: string;
}interface WelcomeState {message: string;
}class Welcome extends Component<WelcomeProps, WelcomeState> {constructor(props: WelcomeProps) {super(props);this.state = {message: 'Welcome to React!'};}render(): React.ReactNode {const { name } = this.props;const { message } = this.state;return (<div><h1>Hello, {name}</h1><p>{message}</p></div>);}
}
2.2.2 Props 与属性
Props 传递方式:
// 基础 Props 传递
const UserProfile = (props) => {return (<div><h2>{props.name}</h2><p>Age: {props.age}</p><p>Email: {props.email}</p></div>);
};// 解构 Props
const UserProfile = ({ name, age, email }) => {return (<div><h2>{name}</h2><p>Age: {age}</p><p>Email: {email}</p></div>);
};// 使用组件
function App() {const user = {name: 'John Doe',age: 30,email: 'john@example.com'};return (<div>{/* 分别传递属性 */}<UserProfile name="Alice" age={25} email="alice@example.com" />{/* 使用展开运算符 */}<UserProfile {...user} />{/* 混合使用 */}<UserProfile {...user} name="Bob" // 覆盖 name 属性/></div>);
}
Props 验证与默认值:
import PropTypes from 'prop-types';const Button = ({ text, onClick, variant = 'primary', disabled = false,size = 'medium'
}) => {const baseClasses = 'btn';const variantClasses = `btn-${variant}`;const sizeClasses = `btn-${size}`;const disabledClasses = disabled ? 'btn-disabled' : '';return (<buttonclassName={`${baseClasses} ${variantClasses} ${sizeClasses} ${disabledClasses}`}onClick={onClick}disabled={disabled}>{text}</button>);
};// Props 类型验证
Button.propTypes = {text: PropTypes.string.isRequired,onClick: PropTypes.func,variant: PropTypes.oneOf(['primary', 'secondary', 'danger', 'success']),disabled: PropTypes.bool,size: PropTypes.oneOf(['small', 'medium', 'large']),
};// 默认 Props
Button.defaultProps = {variant: 'primary',disabled: false,size: 'medium',onClick: () => {},
};// TypeScript 类型定义
interface ButtonProps {text: string;onClick?: () => void;variant?: 'primary' | 'secondary' | 'danger' | 'success';disabled?: boolean;size?: 'small' | 'medium' | 'large';
}const Button: React.FC<ButtonProps> = ({text,onClick,variant = 'primary',disabled = false,size = 'medium'
}) => {// 组件实现
};
2.2.3 Children 与内容
Children 传递方式:
// 基础 Children 使用
const Card = ({ children, title }) => {return (<div className="card">{title && <div className="card-header">{title}</div>}<div className="card-body">{children}</div></div>);
};// 使用 Card 组件
function App() {return (<div>{/* 传递文本内容 */}<Card title="Simple Card">This is a simple card content.</Card>{/* 传递多个元素 */}<Card title="Complex Card"><h3>Subtitle</h3><p>Paragraph content.</p><button>Action Button</button></Card>{/* 空内容 */}<Card title="Empty Card">{/* 可以显示空状态 */}<p>No content available.</p></Card></div>);
}
Children 操作函数:
import React, { Children, cloneElement, isValidElement } from 'react';const TabContainer = ({ children, activeIndex, onTabChange }) => {const tabs = Children.toArray(children);return (<div className="tab-container"><div className="tab-nav">{tabs.map((child, index) => {if (!isValidElement(child)) return null;const isActive = index === activeIndex;const { title } = child.props;return (<buttonkey={index}className={`tab-button ${isActive ? 'active' : ''}`}onClick={() => onTabChange(index)}>{title}</button>);})}</div><div className="tab-content">{tabs.map((child, index) => {if (!isValidElement(child)) return null;return (<divkey={index}className={`tab-panel ${index === activeIndex ? 'active' : ''}`}>{index === activeIndex ? child : null}</div>);})}</div></div>);
};const TabPanel = ({ title, children }) => {return <div>{children}</div>;
};// 使用示例
function App() {const [activeTab, setActiveTab] = React.useState(0);return (<TabContainer activeIndex={activeTab} onTabChange={setActiveTab}><TabPanel title="Profile"><h3>User Profile</h3><p>Profile information goes here.</p></TabPanel><TabPanel title="Settings"><h3>Settings</h3><p>Settings configuration goes here.</p></TabPanel><TabPanel title="About"><h3>About</h3><p>About information goes here.</p></TabPanel></TabContainer>);
}
Children 类型检查:
import { Children, isValidElement } from 'react';const Menu = ({ children }) => {// 检查 children 是否为 MenuItemconst menuItems = Children.toArray(children).filter(child => isValidElement(child) && child.type === MenuItem);if (menuItems.length === 0) {console.warn('Menu should contain at least one MenuItem');return null;}return (<ul className="menu">{menuItems.map((child, index) => (<li key={index} className="menu-item">{child}</li>))}</ul>);
};const MenuItem = ({ children, onClick, disabled = false }) => {return (<button className={`menu-button ${disabled ? 'disabled' : ''}`}onClick={onClick}disabled={disabled}>{children}</button>);
};// 使用示例
function App() {return (<Menu><MenuItem onClick={() => console.log('Home clicked')}>Home</MenuItem><MenuItem onClick={() => console.log('About clicked')}>About</MenuItem><MenuItem onClick={() => console.log('Contact clicked')}>Contact</MenuItem></Menu>);
}
2.3 JSX 表达式与插值
2.3.1 基础表达式插值
JavaScript 表达式嵌入:
import React, { useState } from 'react';const ExpressionDemo = () => {const [count, setCount] = useState(0);const [name, setName] = useState('');const [isLogin, setIsLogin] = useState(false);const items = ['Apple', 'Banana', 'Orange'];const user = {id: 1,name: 'John Doe',email: 'john@example.com'};// 计算属性const greeting = `Hello, ${user.name}!`;const itemCount = items.length;const isEven = count % 2 === 0;return (<div className="expression-demo">{/* 变量插值 */}<h1>{greeting}</h1>{/* 算术运算 */}<p>Count: {count}</p><p>Double: {count * 2}</p><p>Is even: {isEven ? 'Yes' : 'No'}</p>{/* 字符串操作 */}<inputtype="text"value={name}onChange={(e) => setName(e.target.value)}placeholder="Enter your name"/>{name && <p>Hello, {name.toUpperCase()}!</p>}{/* 条件渲染 */}<div>{isLogin ? (<p>Welcome back!</p>) : (<p>Please log in</p>)}</div>{/* 数组操作 */}<ul>{items.map((item, index) => (<li key={index}>{index + 1}. {item}</li>))}</ul>{/* 对象操作 */}<div className="user-card"><h2>{user.name}</h2><p>Email: {user.email}</p><p>ID: {String(user.id).padStart(6, '0')}</p></div>{/* 日期格式化 */}<p>Current time: {new Date().toLocaleTimeString()}</p><p>Formatted date: {new Date().toLocaleDateString('zh-CN')}</p>{/* 函数调用 */}<button onClick={() => setCount(count + 1)}>Increment: {count + 1}</button></div>);
};
2.3.2 条件渲染
多种条件渲染方式:
import React, { useState } from 'react';const ConditionalRendering = () => {const [isLoading, setIsLoading] = useState(false);const [error, setError] = useState(null);const [data, setData] = useState(null);const [userType, setUserType] = useState('guest');// 模拟数据获取const fetchData = async () => {setIsLoading(true);setError(null);try {// 模拟 API 调用await new Promise(resolve => setTimeout(resolve, 1000));setData({ message: 'Data loaded successfully!' });} catch (err) {setError('Failed to load data');} finally {setIsLoading(false);}};// 1. 三元运算符条件渲染const renderStatus = () => {return isLoading ? (<div className="loading">Loading...</div>) : error ? (<div className="error">Error: {error}</div>) : data ? (<div className="success">{data.message}</div>) : (<div className="idle">No data loaded</div>);};return (<div className="conditional-rendering"><h2>Conditional Rendering Examples</h2>{/* 三元运算符 */}<div className="example"><h3>1. Ternary Operator</h3><div className="status">{isLoading ? 'Loading...' : 'Load Complete'}</div></div>{/* 逻辑与 (&&) */}<div className="example"><h3>2. Logical AND</h3>{error && <div className="error">Error: {error}</div>}{data && <div className="data">{data.message}</div>}{!isLoading && !error && !data && (<div className="no-data">No data available</div>)}</div>{/* 函数条件渲染 */}<div className="example"><h3>3. Function Rendering</h3>{renderStatus()}</div>{/* 多条件渲染 */}<div className="example"><h3>4. Multiple Conditions</h3><div className="user-info">{(() => {switch (userType) {case 'admin':return <span className="admin">Admin User</span>;case 'user':return <span className="user">Regular User</span>;case 'guest':return <span className="guest">Guest User</span>;default:return <span className="unknown">Unknown User</span>;}})()}</div><div><label>User Type:</label><select value={userType} onChange={(e) => setUserType(e.target.value)}><option value="guest">Guest</option><option value="user">User</option><option value="admin">Admin</option></select></div></div>{/* 复杂条件渲染 */}<div className="example"><h3>5. Complex Conditions</h3><div className="actions">{data && !isLoading && (<div className="action-buttons">{data.message.includes('success') && (<button className="success-btn">Success Action</button>)}{data.message.includes('error') && (<button className="error-btn">Error Action</button>)}{data.message.length > 20 && (<button className="long-btn">Long Message Action</button>)}</div>)}</div></div>{/* 控制按钮 */}<div className="controls"><button onClick={fetchData} disabled={isLoading}>{isLoading ? 'Loading...' : 'Load Data'}</button><button onClick={() => setError(null)}>Clear Error</button><button onClick={() => setData(null)}>Clear Data</button></div></div>);
};
2.3.3 列表渲染
基础列表渲染:
import React, { useState } from 'react';const ListRendering = () => {const [items, setItems] = useState([{ id: 1, name: 'Apple', price: 2.5, category: 'fruit' },{ id: 2, name: 'Banana', price: 1.2, category: 'fruit' },{ id: 3, name: 'Carrot', price: 0.8, category: 'vegetable' },{ id: 4, name: 'Bread', price: 3.0, category: 'grain' },]);const [filter, setFilter] = useState('all');// 过滤和排序逻辑const filteredItems = items.filter(item => filter === 'all' || item.category === filter).sort((a, b) => a.name.localeCompare(b.name));return (<div className="list-rendering"><h2>List Rendering Examples</h2>{/* 基础列表 */}<div className="example"><h3>1. Basic List</h3><ul>{items.map(item => (<li key={item.id}>{item.name} - ${item.price}</li>))}</ul></div>{/* 带组件的列表 */}<div className="example"><h3>2. List with Components</h3><div className="item-grid">{filteredItems.map(item => (<ItemCard key={item.id} item={item}onEdit={() => console.log('Edit:', item.id)}onDelete={() => {setItems(items.filter(i => i.id !== item.id));}}/>))}</div></div>{/* 分组列表 */}<div className="example"><h3>3. Grouped List</h3><div className="grouped-items">{['fruit', 'vegetable', 'grain'].map(category => {const categoryItems = items.filter(item => item.category === category);if (categoryItems.length === 0) return null;return (<div key={category} className="category-group"><h4>{category.charAt(0).toUpperCase() + category.slice(1)}</h4><ul>{categoryItems.map(item => (<li key={item.id}>{item.name}</li>))}</ul></div>);})}</div></div>{/* 动态列表操作 */}<div className="example"><h3>4. Dynamic List Operations</h3><div className="filters"><button onClick={() => setFilter('all')}className={filter === 'all' ? 'active' : ''}>All</button><button onClick={() => setFilter('fruit')}className={filter === 'fruit' ? 'active' : ''}>Fruits</button><button onClick={() => setFilter('vegetable')}className={filter === 'vegetable' ? 'active' : ''}>Vegetables</button><button onClick={() => setFilter('grain')}className={filter === 'grain' ? 'active' : ''}>Grains</button></div><button onClick={() => {const newItem = {id: Date.now(),name: `New Item ${items.length + 1}`,price: Math.random() * 5 + 0.5,category: 'fruit'};setItems([...items, newItem]);}}>Add Item</button></div>{/* 嵌套列表 */}<div className="example"><h3>5. Nested List</h3><div className="nested-list">{[{title: 'Fruits',items: ['Apple', 'Banana', 'Orange']},{title: 'Vegetables', items: ['Carrot', 'Broccoli', 'Spinach']},{title: 'Grains',items: ['Wheat', 'Rice', 'Corn']}].map(category => (<div key={category.title} className="category"><h4>{category.title}</h4><ul>{category.items.map((item, index) => (<li key={index}>{item}</li>))}</ul></div>))}</div></div></div>);
};// 列表项组件
const ItemCard = ({ item, onEdit, onDelete }) => {const [isHovered, setIsHovered] = useState(false);return (<div className="item-card"onMouseEnter={() => setIsHovered(true)}onMouseLeave={() => setIsHovered(false)}><h4>{item.name}</h4><p>Price: ${item.price.toFixed(2)}</p><p>Category: {item.category}</p>{isHovered && (<div className="actions"><button onClick={onEdit}>Edit</button><button onClick={onDelete}>Delete</button></div>)}</div>);
};
2.4 JSX 事件处理
2.4.1 事件绑定语法
基础事件处理:
import React, { useState } from 'react';const EventHandling = () => {const [count, setCount] = useState(0);const [message, setMessage] = useState('');const [isToggle, setIsToggle] = useState(false);// 基础事件处理函数const handleClick = () => {setCount(count + 1);console.log('Button clicked!');};// 带参数的事件处理const handleIncrement = (amount) => {setCount(count + amount);};// 使用箭头函数传递参数const handleButtonClick = (value) => {setMessage(`Button ${value} clicked!`);};// 表单事件处理const handleInputChange = (e) => {setMessage(e.target.value);};const handleSubmit = (e) => {e.preventDefault(); // 阻止默认行为alert(`Form submitted with: ${message}`);};// 键盘事件处理const handleKeyPress = (e) => {if (e.key === 'Enter') {setMessage(`Enter pressed! Current count: ${count}`);}};// 鼠标事件处理const handleMouseEnter = () => {setIsToggle(true);};const handleMouseLeave = () => {setIsToggle(false);};return (<div className="event-handling"><h2>Event Handling Examples</h2>{/* 基础点击事件 */}<div className="example"><h3>1. Basic Click Event</h3><button onClick={handleClick}>Click Count: {count}</button><button onClick={() => handleIncrement(5)}>Add 5</button><button onClick={() => handleIncrement(-1)}>Subtract 1</button></div>{/* 表单事件 */}<div className="example"><h3>2. Form Events</h3><form onSubmit={handleSubmit}><inputtype="text"value={message}onChange={handleInputChange}onKeyPress={handleKeyPress}placeholder="Type something..."/><button type="submit">Submit</button></form>{message && <p>Message: {message}</p>}</div>{/* 按钮组事件 */}<div className="example"><h3>3. Button Group Events</h3>{['A', 'B', 'C'].map((value) => (<buttonkey={value}onClick={() => handleButtonClick(value)}style={{ margin: '5px' }}>Button {value}</button>))}</div>{/* 鼠标事件 */}<div className="example"><h3>4. Mouse Events</h3><divclassName={`hover-box ${isToggle ? 'active' : ''}`}onMouseEnter={handleMouseEnter}onMouseLeave={handleMouseLeave}style={{width: '200px',height: '100px',border: '2px solid #ccc',display: 'flex',alignItems: 'center',justifyContent: 'center',backgroundColor: isToggle ? '#e0e0e0' : '#f5f5f5',cursor: 'pointer'}}>Hover over me!</div>{isToggle && <p>Mouse is over the box!</p>}</div>{/* 多种事件组合 */}<div className="example"><h3>5. Multiple Events</h3><divonClick={() => console.log('Div clicked')}onDoubleClick={() => console.log('Div double-clicked')}onMouseDown={() => console.log('Mouse down')}onMouseUp={() => console.log('Mouse up')}style={{width: '300px',height: '150px',border: '2px solid blue',display: 'flex',alignItems: 'center',justifyContent: 'center',backgroundColor: '#f0f8ff',cursor: 'pointer'}}>Click, double-click, or interact with this area</div></div>{/* 键盘事件 */}<div className="example"><h3>6. Keyboard Events</h3><divtabIndex={0} // 使 div 可聚焦onKeyDown={(e) => {console.log('Key pressed:', e.key);setMessage(`Key pressed: ${e.key}`);}}onKeyUp={(e) => {console.log('Key released:', e.key);}}style={{width: '300px',height: '100px',border: '2px solid green',display: 'flex',alignItems: 'center',justifyContent: 'center',backgroundColor: '#f0fff0'}}>Press any key here</div></div></div>);
};
2.4.2 事件对象与参数
事件对象详解:
import React, { useState } from 'react';const EventObjectDemo = () => {const [mousePosition, setMousePosition] = useState({ x: 0, y: 0 });const [keyInfo, setKeyInfo] = useState({ key: '', code: '', ctrlKey: false });const [formValue, setFormValue] = useState('');// 鼠标事件处理const handleMouseMove = (e) => {setMousePosition({x: e.clientX,y: e.clientY});};const handleMouseClick = (e) => {console.log('Mouse click event:', e);console.log('Target:', e.target);console.log('Current target:', e.currentTarget);console.log('Button:', e.button); // 0=左键, 1=中键, 2=右键console.log('Coordinates:', { x: e.clientX, y: e.clientY });};// 键盘事件处理const handleKeyDown = (e) => {setKeyInfo({key: e.key,code: e.code,ctrlKey: e.ctrlKey,shiftKey: e.shiftKey,altKey: e.altKey});// 阻止特定键的默认行为if (e.key === 'F1') {e.preventDefault();alert('F1 key is disabled');}};// 表单事件处理const handleFormChange = (e) => {const { name, value, type, checked } = e.target;if (type === 'checkbox') {setFormValue(checked);} else {setFormValue(value);}console.log('Form input change:', {name,value: type === 'checkbox' ? checked : value,type});};// 自定义事件参数传递const handleAction = (action, value, event) => {console.log('Action:', action);console.log('Value:', value);console.log('Event:', event);// 访问事件对象if (event) {event.stopPropagation(); // 阻止事件冒泡}};return (<div className="event-object-demo" onMouseMove={handleMouseMove}><h2>Event Object Examples</h2>{/* 鼠标事件对象 */}<div className="example"><h3>1. Mouse Event Object</h3><div onClick={handleMouseClick}style={{width: '300px',height: '150px',border: '2px solid blue',display: 'flex',alignItems: 'center',justifyContent: 'center',backgroundColor: '#e6f3ff',cursor: 'pointer',margin: '10px 0'}}>Click here to see event details</div><div style={{ padding: '10px', backgroundColor: '#f0f0f0' }}><strong>Mouse Position:</strong> X: {mousePosition.x}, Y: {mousePosition.y}</div></div>{/* 键盘事件对象 */}<div className="example"><h3>2. Keyboard Event Object</h3><inputtype="text"onKeyDown={handleKeyDown}placeholder="Press any key"style={{ padding: '8px', width: '300px' }}/><div style={{ marginTop: '10px', padding: '10px', backgroundColor: '#f0f0f0' }}><strong>Key Info:</strong><br />Key: {keyInfo.key}<br />Code: {keyInfo.code}<br />Ctrl: {keyInfo.ctrlKey ? 'Yes' : 'No'}<br />Shift: {keyInfo.shiftKey ? 'Yes' : 'No'}</div></div>{/* 表单事件对象 */}<div className="example"><h3>3. Form Event Object</h3><div><label>Text Input:<inputtype="text"name="textInput"value={typeof formValue === 'string' ? formValue : ''}onChange={handleFormChange}placeholder="Type something"style={{ marginLeft: '10px', padding: '5px' }}/></label></div><div style={{ marginTop: '10px' }}><label>Checkbox:<inputtype="checkbox"name="checkbox"checked={typeof formValue === 'boolean' ? formValue : false}onChange={handleFormChange}style={{ marginLeft: '10px' }}/>Value: {formValue.toString()}</label></div></div>{/* 自定义参数传递 */}<div className="example"><h3>4. Custom Parameter Passing</h3>{['Save', 'Delete', 'Edit'].map((action) => (<buttonkey={action}onClick={(e) => handleAction(action.toLowerCase(), `data-${action.toLowerCase()}`, e)}style={{ margin: '5px', padding: '8px 16px' }}>{action}</button>))}<div style={{ marginTop: '10px', padding: '10px', backgroundColor: '#f0f0f0' }}>Check the console for event details</div></div>{/* 事件冒泡演示 */}<div className="example"><h3>5. Event Bubbling</h3><divonClick={() => console.log('Outer div clicked')}style={{width: '300px',height: '200px',border: '3px solid red',padding: '20px',backgroundColor: '#ffe6e6'}}><p>Outer Div (Click me)</p><divonClick={(e) => {e.stopPropagation();console.log('Inner div clicked (no bubbling)');}}style={{width: '200px',height: '100px',border: '2px solid green',display: 'flex',alignItems: 'center',justifyContent: 'center',backgroundColor: '#e6ffe6',cursor: 'pointer'}}>Inner Div (No Bubbling)</div></div></div>{/* 事件委托演示 */}<div className="example"><h3>6. Event Delegation</h3><divonClick={(e) => {if (e.target.tagName === 'BUTTON') {const action = e.target.textContent;console.log(`Delegated action: ${action}`);}}}style={{ padding: '10px', border: '2px solid orange' }}><p>Click any button (event delegation)</p>{['Action 1', 'Action 2', 'Action 3'].map(action => (<button key={action} style={{ margin: '5px' }}>{action}</button>))}</div></div></div>);
};
2.4.3 高级事件处理模式
事件处理最佳实践:
import React, { useState, useCallback, useMemo, useRef } from 'react';const AdvancedEventHandling = () => {const [items, setItems] = useState([{ id: 1, text: 'Item 1', completed: false },{ id: 2, text: 'Item 2', completed: true },{ id: 3, text: 'Item 3', completed: false },]);const [filter, setFilter] = useState('all');const [searchTerm, setSearchTerm] = useState('');const inputRef = useRef(null);// 使用 useCallback 优化事件处理函数const handleToggleItem = useCallback((id) => {setItems(prevItems =>prevItems.map(item =>item.id === id ? { ...item, completed: !item.completed } : item));}, []);const handleDeleteItem = useCallback((id) => {setItems(prevItems => prevItems.filter(item => item.id !== id));}, []);const handleAddItem = useCallback(() => {if (inputRef.current && inputRef.current.value.trim()) {const newItem = {id: Date.now(),text: inputRef.current.value.trim(),completed: false};setItems(prevItems => [...prevItems, newItem]);inputRef.current.value = '';inputRef.current.focus();}}, []);const handleKeyPress = useCallback((e) => {if (e.key === 'Enter') {handleAddItem();}}, [handleAddItem]);// 使用 useMemo 优化计算const filteredItems = useMemo(() => {return items.filter(item => {const matchesFilter = filter === 'all' ||(filter === 'completed' && item.completed) ||(filter === 'active' && !item.completed);const matchesSearch = item.text.toLowerCase().includes(searchTerm.toLowerCase());return matchesFilter && matchesSearch;});}, [items, filter, searchTerm]);const stats = useMemo(() => ({total: items.length,completed: items.filter(item => item.completed).length,active: items.filter(item => !item.completed).length}), [items]);// 防抖搜索const debouncedSearch = useCallback(debounce((value) => {setSearchTerm(value);}, 300),[]);return (<div className="advanced-event-handling"><h2>Advanced Event Handling</h2>{/* 统计信息 */}<div className="stats"><span>Total: {stats.total}</span><span>Active: {stats.active}</span><span>Completed: {stats.completed}</span></div>{/* 添加项目 */}<div className="add-item"><inputref={inputRef}type="text"onKeyPress={handleKeyPress}placeholder="Add new item..."/><button onClick={handleAddItem}>Add</button></div>{/* 搜索和过滤 */}<div className="filters"><inputtype="text"onChange={(e) => debouncedSearch(e.target.value)}placeholder="Search items..."/><div className="filter-buttons">{['all', 'active', 'completed'].map(filterType => (<buttonkey={filterType}onClick={() => setFilter(filterType)}className={filter === filterType ? 'active' : ''}>{filterType.charAt(0).toUpperCase() + filterType.slice(1)}</button>))}</div></div>{/* 项目列表 */}<div className="item-list">{filteredItems.map(item => (<TodoItemkey={item.id}item={item}onToggle={handleToggleItem}onDelete={handleDeleteItem}/>))}</div>{/* 批量操作 */}<div className="bulk-actions"><button onClick={() => setItems(items.map(item => ({ ...item, completed: true })))}>Complete All</button><button onClick={() => setItems(items.map(item => ({ ...item, completed: false })))}>Reset All</button><button onClick={() => setItems(items.filter(item => !item.completed))}>Clear Completed</button></div></div>);
};// TodoItem 组件
const TodoItem = React.memo(({ item, onToggle, onDelete }) => {const handleToggle = useCallback(() => {onToggle(item.id);}, [item.id, onToggle]);const handleDelete = useCallback(() => {onDelete(item.id);}, [item.id, onDelete]);return (<div className={`todo-item ${item.completed ? 'completed' : 'active'}`}style={{display: 'flex',alignItems: 'center',padding: '8px',margin: '4px 0',backgroundColor: item.completed ? '#f0f0f0' : 'white',border: '1px solid #ddd',borderRadius: '4px'}}><inputtype="checkbox"checked={item.completed}onChange={handleToggle}style={{ marginRight: '8px' }}/><span style={{textDecoration: item.completed ? 'line-through' : 'none',flex: 1,color: item.completed ? '#888' : '#000'}}>{item.text}</span><buttononClick={handleDelete}style={{padding: '4px 8px',backgroundColor: '#ff4444',color: 'white',border: 'none',borderRadius: '4px',cursor: 'pointer'}}>Delete</button></div>);
});// 防抖函数
function debounce(func, wait) {let timeout;return function executedFunction(...args) {const later = () => {clearTimeout(timeout);func(...args);};clearTimeout(timeout);timeout = setTimeout(later, wait);};
}export default AdvancedEventHandling;
2.5 JSX 样式与 CSS
2.5.1 内联样式
内联样式基础:
import React, { useState } from 'react';const InlineStyles = () => {const [isHovered, setIsHovered] = useState(false);const [theme, setTheme] = useState('light');// 样式对象定义const containerStyle = {maxWidth: '800px',margin: '0 auto',padding: '20px',backgroundColor: theme === 'light' ? '#ffffff' : '#1a1a1a',color: theme === 'light' ? '#000000' : '#ffffff',borderRadius: '8px',boxShadow: '0 2px 10px rgba(0, 0, 0, 0.1)'};const buttonStyle = {padding: '12px 24px',margin: '5px',border: 'none',borderRadius: '6px',fontSize: '16px',cursor: 'pointer',transition: 'all 0.3s ease',backgroundColor: '#007bff',color: '#ffffff'};const hoverButtonStyle = {...buttonStyle,backgroundColor: isHovered ? '#0056b3' : '#007bff',transform: isHovered ? 'translateY(-2px)' : 'translateY(0)',boxShadow: isHovered ? '0 4px 12px rgba(0, 123, 255, 0.3)' : 'none'};const textStyle = {fontSize: '18px',lineHeight: '1.6',marginBottom: '20px',fontFamily: 'Arial, sans-serif'};const cardStyle = {backgroundColor: theme === 'light' ? '#f8f9fa' : '#2d2d2d',padding: '20px',margin: '15px 0',borderRadius: '8px',border: `1px solid ${theme === 'light' ? '#dee2e6' : '#404040'}`,transition: 'transform 0.2s ease'};return (<div style={containerStyle}><h1 style={{ textAlign: 'center', marginBottom: '30px' }}>Inline Styles Example</h1>{/* 基础内联样式 */}<div><p style={textStyle}>This paragraph uses inline styling with a style object.</p>{/* 动态样式 */}<div style={cardStyle}><h3>Dynamic Styling</h3><p>Theme: {theme}</p><button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}style={buttonStyle}>Toggle Theme</button></div>{/* 交互样式 */}<div style={cardStyle}><h3>Interactive Styling</h3><divstyle={hoverButtonStyle}onMouseEnter={() => setIsHovered(true)}onMouseLeave={() => setIsHovered(false)}>Hover over me!</div></div>{/* 条件样式 */}<div style={cardStyle}><h3>Conditional Styling</h3>{['Success', 'Warning', 'Error', 'Info'].map((type) => {const colors = {Success: { bg: '#d4edda', color: '#155724' },Warning: { bg: '#fff3cd', color: '#856404' },Error: { bg: '#f8d7da', color: '#721c24' },Info: { bg: '#d1ecf1', color: '#0c5460' }};return (<divkey={type}style={{backgroundColor: colors[type].bg,color: colors[type].color,padding: '10px',margin: '5px 0',borderRadius: '4px',border: `1px solid ${colors[type].color}`}}>{type} Message</div>);})}</div>{/* 计算样式 */}<div style={cardStyle}><h3>Computed Styles</h3>{[1, 2, 3, 4, 5].map((num) => (<divkey={num}style={{width: `${num * 20}px`,height: '30px',backgroundColor: `hsl(${num * 60}, 70%, 50%)`,margin: '5px',borderRadius: '4px',display: 'inline-block',textAlign: 'center',lineHeight: '30px',color: 'white',fontWeight: 'bold'}}>{num}</div>))}</div>{/* 响应式样式模拟 */}<div style={cardStyle}><h3>Responsive Styles (simulated)</h3><divstyle={{display: 'grid',gridTemplateColumns: window.innerWidth > 768 ? 'repeat(3, 1fr)' : '1fr',gap: '10px'}}>{['Card 1', 'Card 2', 'Card 3'].map((text, index) => (<divkey={index}style={{padding: '20px',backgroundColor: theme === 'light' ? '#e9ecef' : '#404040',borderRadius: '8px',textAlign: 'center'}}>{text}</div>))}</div></div></div></div>);
};
2.5.2 CSS 模块化
CSS Modules 使用:
// styles/Component.module.css
.container {max-width: 800px;margin: 0 auto;padding: 20px;background-color: #ffffff;border-radius: 8px;box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}.title {color: #2c3e50;font-size: 2rem;text-align: center;margin-bottom: 2rem;
}.button {padding: 12px 24px;border: none;border-radius: 6px;font-size: 16px;cursor: pointer;transition: all 0.3s ease;
}.buttonPrimary {background-color: #007bff;color: white;
}.buttonPrimary:hover {background-color: #0056b3;transform: translateY(-2px);
}.buttonSecondary {background-color: #6c757d;color: white;
}.buttonSecondary:hover {background-color: #5a6268;
}.card {background-color: #f8f9fa;padding: 20px;margin: 15px 0;border-radius: 8px;border: 1px solid #dee2e6;
}.cardDark {background-color: #2d2d2d;border-color: #404040;color: white;
}/* 组合选择器 */
.container :global(.global-class) {color: red;
}/* 变量 */
:root {--primary-color: #007bff;--secondary-color: #6c757d;--success-color: #28a745;--warning-color: #ffc107;--error-color: #dc3545;
}
// components/StyledComponent.jsx
import React, { useState } from 'react';
import styles from '../styles/Component.module.css';const StyledComponent = () => {const [theme, setTheme] = useState('light');const [isActive, setIsActive] = useState(false);return (<div className={styles.container}><h1 className={styles.title}>CSS Modules Example</h1>{/* 基础样式使用 */}<div className={styles.card}><h3>Basic CSS Modules</h3><p>This card uses CSS Modules for styling.</p><button className={styles.button}>Basic Button</button></div>{/* 样式组合 */}<div className={styles.card}><h3>Style Composition</h3><button className={`${styles.button} ${styles.buttonPrimary}`}>Primary Button</button><button className={`${styles.button} ${styles.buttonSecondary}`}>Secondary Button</button></div>{/* 条件样式 */}<div className={`${styles.card} ${theme === 'dark' ? styles.cardDark : ''}`}><h3>Conditional Styling</h3><p>Current theme: {theme}</p><button className={`${styles.button} ${styles.buttonPrimary}`}onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>Toggle Theme</button></div>{/* 动态样式类 */}<div className={styles.card}><h3>Dynamic Classes</h3><buttonclassName={`${styles.button} ${isActive ? styles.buttonPrimary : styles.buttonSecondary}`}onClick={() => setIsActive(!isActive)}>{isActive ? 'Active' : 'Inactive'}</button></div>{/* 使用 CSS 变量 */}<div className={styles.card}style={{'--primary-color': '#ff6b6b','--secondary-color': '#4ecdc4'}}><h3>CSS Variables</h3><div className={styles.button}style={{backgroundColor: 'var(--primary-color)',color: 'white'}}>Custom Primary</div><div className={styles.button}style={{backgroundColor: 'var(--secondary-color)',color: 'white'}}>Custom Secondary</div></div>{/* 响应式样式 */}<div className={`${styles.card} ${styles.responsiveCard}`}><h3>Responsive Design</h3><p>This card adapts to different screen sizes.</p></div></div>);
};export default StyledComponent;
2.5.3 CSS-in-JS 方案
使用 styled-components:
import React, { useState } from 'react';
import styled, { css, ThemeProvider, createGlobalStyle } from 'styled-components';// 全局样式
const GlobalStyle = createGlobalStyle`* {margin: 0;padding: 0;box-sizing: border-box;}body {font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;background-color: ${props => props.theme.backgroundColor};color: ${props => props.theme.textColor};transition: all 0.3s ease;}
`;// 主题定义
const lightTheme = {backgroundColor: '#ffffff',textColor: '#333333',primaryColor: '#007bff',secondaryColor: '#6c757d',successColor: '#28a745',warningColor: '#ffc107',errorColor: '#dc3545',borderRadius: '8px',boxShadow: '0 2px 10px rgba(0, 0, 0, 0.1)'
};const darkTheme = {backgroundColor: '#1a1a1a',textColor: '#ffffff',primaryColor: '#0d6efd',secondaryColor: '#6c757d',successColor: '#198754',warningColor: '#ffc107',errorColor: '#dc3545',borderRadius: '8px',boxShadow: '0 2px 10px rgba(255, 255, 255, 0.1)'
};// 样式组件定义
const Container = styled.div`max-width: 800px;margin: 0 auto;padding: 2rem;background-color: ${props => props.theme.backgroundColor};border-radius: ${props => props.theme.borderRadius};box-shadow: ${props => props.theme.boxShadow};
`;const Title = styled.h1`color: ${props => props.theme.textColor};font-size: 2rem;text-align: center;margin-bottom: 2rem;font-weight: 600;
`;const Button = styled.button`padding: 12px 24px;border: none;border-radius: 6px;font-size: 16px;cursor: pointer;transition: all 0.3s ease;font-weight: 500;${props => {switch (props.variant) {case 'primary':return css`background-color: ${props.theme.primaryColor};color: white;&:hover {background-color: ${props.theme.primaryColor}dd;transform: translateY(-2px);box-shadow: 0 4px 12px ${props.theme.primaryColor}66;}`;case 'secondary':return css`background-color: ${props.theme.secondaryColor};color: white;&:hover {background-color: ${props.theme.secondaryColor}dd;}`;case 'success':return css`background-color: ${props.theme.successColor};color: white;&:hover {background-color: ${props.theme.successColor}dd;}`;case 'warning':return css`background-color: ${props.theme.warningColor};color: black;&:hover {background-color: ${props.theme.warningColor}dd;}`;case 'error':return css`background-color: ${props.theme.errorColor};color: white;&:hover {background-color: ${props.theme.errorColor}dd;}`;default:return css`background-color: ${props.theme.primaryColor};color: white;`;}}}${props => props.size === 'small' && css`padding: 8px 16px;font-size: 14px;`}${props => props.size === 'large' && css`padding: 16px 32px;font-size: 18px;`}${props => props.fullWidth && css`width: 100%;display: block;`}&:disabled {opacity: 0.6;cursor: not-allowed;transform: none !important;}
`;const Card = styled.div`background-color: ${props => props.theme === 'dark' ? '#2d2d2d' : '#f8f9fa'};padding: 20px;margin: 15px 0;border-radius: ${props => props.theme.borderRadius};border: 1px solid ${props => props.theme === 'dark' ? '#404040' : '#dee2e6'};transition: all 0.3s ease;&:hover {transform: translateY(-2px);box-shadow: ${props => props.theme === 'dark' ? '0 4px 20px rgba(255, 255, 255, 0.1)' : '0 4px 20px rgba(0, 0, 0, 0.1)'};}
`;const Input = styled.input`width: 100%;padding: 12px;border: 2px solid ${props => props.theme === 'dark' ? '#404040' : '#dee2e6'};border-radius: 6px;font-size: 16px;background-color: ${props => props.theme === 'dark' ? '#1a1a1a' : 'white'};color: ${props => props.theme === 'dark' ? 'white' : 'black'};transition: all 0.3s ease;&:focus {outline: none;border-color: ${props => props.theme.primaryColor};box-shadow: 0 0 0 3px ${props => props.theme.primaryColor}33;}&::placeholder {color: ${props => props.theme === 'dark' ? '#888' : '#999'};}
`;const Grid = styled.div`display: grid;gap: 20px;margin: 20px 0;${props => props.columns === 2 && css`grid-template-columns: repeat(2, 1fr);`}${props => props.columns === 3 && css`grid-template-columns: repeat(3, 1fr);`}${props => props.columns === 4 && css`grid-template-columns: repeat(4, 1fr);`}@media (max-width: 768px) {grid-template-columns: 1fr;}
`;const CSSInJSExample = () => {const [theme, setTheme] = useState('light');const [inputValue, setInputValue] = useState('');const [isLoading, setIsLoading] = useState(false);const currentTheme = theme === 'light' ? lightTheme : darkTheme;return (<ThemeProvider theme={currentTheme}><GlobalStyle /><Container><Title>CSS-in-JS with Styled Components</Title>{/* 主题切换 */}<Card><h3>Theme Switching</h3><p>Current theme: {theme}</p><Button variant="primary"onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>Toggle Theme</Button></Card>{/* 按钮变体 */}<Card><h3>Button Variants</h3><Grid columns={3}><Button variant="primary">Primary</Button><Button variant="secondary">Secondary</Button><Button variant="success">Success</Button><Button variant="warning">Warning</Button><Button variant="error">Error</Button><Button variant="primary" disabled={isLoading}onClick={() => {setIsLoading(true);setTimeout(() => setIsLoading(false), 2000);}}>{isLoading ? 'Loading...' : 'Click Me'}</Button></Grid></Card>{/* 按钮尺寸 */}<Card><h3>Button Sizes</h3><div style={{ display: 'flex', gap: '10px', alignItems: 'center' }}><Button variant="primary" size="small">Small</Button><Button variant="primary">Medium</Button><Button variant="primary" size="large">Large</Button><Button variant="primary" fullWidth>Full Width</Button></div></Card>{/* 表单元素 */}<Card><h3>Form Elements</h3><Inputtype="text"placeholder="Type something..."value={inputValue}onChange={(e) => setInputValue(e.target.value)}/><p style={{ marginTop: '10px' }}>Input value: {inputValue}</p></Card>{/* 动态样式 */}<Card><h3>Dynamic Styling</h3>{[1, 2, 3, 4, 5].map((num) => (<Buttonkey={num}variant="primary"style={{margin: '5px',backgroundColor: `hsl(${num * 60}, 70%, 50%)`,borderColor: `hsl(${num * 60}, 70%, 40%)`}}>Color {num}</Button>))}</Card>{/* 响应式网格 */}<Card><h3>Responsive Grid</h3><Grid columns={3}>{['Card 1', 'Card 2', 'Card 3', 'Card 4', 'Card 5', 'Card 6'].map((text) => (<Card key={text}><h4>{text}</h4><p>Responsive card content</p></Card>))}</Grid></Card></Container></ThemeProvider>);
};export default CSSInJSExample;
通过本章的 JSX 语法详解,你已经掌握了 React 开发的核心语法,包括 JSX 基础概念、表达式插值、事件处理、样式管理等关键内容。这些知识是构建 React 应用的基础,下一章我们将深入学习 React 组件系统。