01-React基础入门——02-JSX语法详解
·

JSX语法详解
一、JSX是什么?
1.1 5W1H分析
/**
* 5W1H 分析:JSX
*
* What: JavaScript XML,在 JavaScript 中编写 HTML 的语法扩展
* Why: 声明式 UI 开发,代码更直观、更易维护
* Who: 所有 React 开发者
* When: 编写 React 组件时
* Where: React 组件的 return 语句中
* How: 在 JS 中直接写 HTML 标签,使用 {} 嵌入表达式
*/
console.log("=" .repeat(60));
console.log("React 19 学习路线 - 第2篇:JSX语法详解");
console.log("=" .repeat(60));
// JSX 示例
const element = <h1 className="greeting">Hello, React 19!</h1>;
// 编译后的 JavaScript
// const element = React.createElement('h1', { className: 'greeting' }, 'Hello, React 19!');
1.2 为什么使用JSX?
/**
* JSX 的优势
* 1. 声明式:代码更直观,易于理解
* 2. 组件化:便于构建可复用的UI组件
* 3. 类型安全:编译时检查错误
* 4. 性能优化:编译时进行优化
*/
// 不使用JSX(纯JavaScript)
const withoutJSX = React.createElement(
'div',
{ className: 'container' },
React.createElement('h1', null, '标题'),
React.createElement('p', null, '段落内容')
);
// 使用JSX(推荐)
const withJSX = (
<div className="container">
<h1>标题</h1>
<p>段落内容</p>
</div>
);
二、JSX基础语法
2.1 嵌入变量和表达式
function ExpressionDemo() {
// 1. 嵌入变量
const name = "React学习者";
const age = 25;
const isActive = true;
const user = { firstName: '张', lastName: '三' };
// 2. 嵌入表达式
const fullName = `${user.firstName}${user.lastName}`;
const isAdult = age >= 18;
const greeting = `你好,${name}!`;
// 3. 嵌入函数调用
const formatDate = (date) => {
return `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}`;
};
// 4. 嵌入运算
const doubleAge = age * 2;
const messageCount = 5;
return (
<div className="expression-demo">
{/* 嵌入变量 */}
<h2>{greeting}</h2>
{/* 嵌入表达式 */}
<p>全名: {fullName}</p>
<p>是否成年: {isAdult ? '是' : '否'}</p>
{/* 嵌入函数调用 */}
<p>当前日期: {formatDate(new Date())}</p>
{/* 嵌入运算 */}
<p>年龄的两倍: {doubleAge}</p>
{/* 条件显示 */}
{messageCount > 0 && (
<div className="notification">
你有 {messageCount} 条新消息
</div>
)}
</div>
);
}
2.2 属性绑定
function AttributeBinding() {
// 1. 标准属性
const className = "btn-primary";
const disabled = true;
const id = "submit-btn";
// 2. 布尔属性
const isChecked = true;
const isDisabled = false;
// 3. 事件属性(驼峰命名)
const handleClick = () => console.log('clicked');
const handleMouseEnter = () => console.log('mouse entered');
// 4. 动态属性名
const attributeName = "data-custom-id";
const attributeValue = "12345";
// 5. 展开属性(Spread Attributes)
const buttonProps = {
type: "button",
className: "btn",
onClick: () => console.log('clicked'),
disabled: false
};
// 6. 条件属性
const isPrimary = true;
const buttonClass = `btn ${isPrimary ? 'btn-primary' : 'btn-secondary'}`;
return (
<div className="attribute-demo">
{/* 标准属性 */}
<button id={id} className={className} disabled={disabled}>
提交
</button>
{/* 布尔属性 */}
<input type="checkbox" checked={isChecked} readOnly />
<button disabled={isDisabled}>不可用按钮</button>
{/* 事件属性 */}
<button onClick={handleClick} onMouseEnter={handleMouseEnter}>
悬停或点击
</button>
{/* 动态属性名 */}
<div {...{ [attributeName]: attributeValue }}>
动态属性内容
</div>
{/* 展开属性 */}
<button {...buttonProps}>展开属性按钮</button>
{/* 条件属性 */}
<button className={buttonClass}>条件样式按钮</button>
</div>
);
}
2.3 子元素
function ChildrenDemo() {
// 1. 字符串子元素
const textElement = <div>纯文本内容</div>;
// 2. 元素子元素
const elementElement = (
<div>
<h1>标题</h1>
<p>段落</p>
</div>
);
// 3. 表达式子元素
const items = ['苹果', '香蕉', '橙子'];
const listElement = (
<ul>
{items.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
);
// 4. 函数子元素(render props)
const DataProvider = ({ render }) => {
const data = { name: 'React', version: '19' };
return render(data);
};
// 5. 混合子元素
const MixedChildren = () => (
<div>
文本内容
<span>元素内容</span>
{[1, 2, 3].map(n => (
<span key={n}> {n} </span>
))}
<DataProvider render={data => (
<p>{data.name} v{data.version}</p>
)} />
</div>
);
return (
<div className="children-demo">
{textElement}
{elementElement}
{listElement}
<MixedChildren />
</div>
);
}
2.4 注释语法
function CommentsDemo() {
// JavaScript单行注释(在JSX外部)
/* JavaScript多行注释(在JSX外部) */
return (
<div className="comments-demo">
{/* JSX注释语法 */}
{/*
多行JSX注释
可以写多行内容
*/}
<h1>标题</h1>
{/* 条件渲染中的注释 */}
{true && (
/* 内联注释 */
<p>显示内容</p>
)}
{/* 使用注释来说明代码意图 */}
<button
onClick={() => console.log('clicked')}
/* 这是一个重要的按钮 */
className="important-btn"
>
重要按钮
</button>
</div>
);
}
三、JSX中的条件渲染
3.1 if语句方式
function IfConditionDemo({ isLoggedIn, userRole }) {
// 方式1:组件内使用if语句
let greeting;
if (isLoggedIn) {
greeting = <h2>欢迎回来!</h2>;
} else {
greeting = <h2>请先登录</h2>;
}
// 方式2:立即执行函数(IIFE)
const renderContent = () => {
if (userRole === 'admin') {
return <AdminPanel />;
} else if (userRole === 'user') {
return <UserPanel />;
} else {
return <GuestPanel />;
}
};
return (
<div className="if-demo">
{greeting}
{renderContent()}
</div>
);
}
// 辅助组件
function AdminPanel() { return <div>管理员面板</div>; }
function UserPanel() { return <div>用户面板</div>; }
function GuestPanel() { return <div>游客面板</div>; }
3.2 三元运算符
function TernaryDemo({ isLoggedIn, theme, hasNotification }) {
return (
<div className="ternary-demo">
{/* 基础三元运算符 */}
<h2>{isLoggedIn ? '欢迎回来' : '请登录'}</h2>
{/* 嵌套三元运算符(谨慎使用) */}
<p>
状态: {isLoggedIn
? '已登录'
: theme === 'dark'
? '暗色模式游客'
: '亮色模式游客'
}
</p>
{/* 三元运算符渲染组件 */}
<div>
{isLoggedIn ? (
<UserDashboard />
) : (
<LoginForm />
)}
</div>
{/* 三元运算符设置属性 */}
<button className={hasNotification ? 'has-notification' : ''}>
消息
{hasNotification && <span className="badge">!</span>}
</button>
</div>
);
}
3.3 &&运算符
function AndOperatorDemo({ isLoggedIn, notifications, user }) {
return (
<div className="and-demo">
{/* 基础&&用法 */}
{isLoggedIn && <WelcomeMessage />}
{/* 与0的陷阱(0 && something 会渲染0) */}
{notifications.length > 0 && (
<div className="notification">
你有 {notifications.length} 条通知
</div>
)}
{/* 链式&& */}
{isLoggedIn && user.isVIP && <VIPBadge />}
{/* 多条件组合 */}
{isLoggedIn && notifications.length > 0 && (
<NotificationList notifications={notifications} />
)}
{/* 防止渲染false */}
{isLoggedIn && null} {/* 不渲染任何内容 */}
</div>
);
}
3.4 switch语句
function SwitchDemo({ status }) {
// 方式1:switch语句
let statusMessage;
switch (status) {
case 'loading':
statusMessage = <LoadingSpinner />;
break;
case 'success':
statusMessage = <SuccessMessage />;
break;
case 'error':
statusMessage = <ErrorMessage />;
break;
default:
statusMessage = <InitialState />;
}
// 方式2:对象映射(推荐)
const statusComponents = {
loading: <LoadingSpinner />,
success: <SuccessMessage />,
error: <ErrorMessage />,
idle: <InitialState />
};
return (
<div className="switch-demo">
{statusMessage}
{statusComponents[status] || statusComponents.idle}
</div>
);
}
四、JSX中的列表渲染
4.1 基础列表渲染
function BasicListDemo() {
// 1. 数组渲染
const fruits = ['苹果', '香蕉', '橙子', '葡萄'];
// 2. 对象数组渲染
const users = [
{ id: 1, name: '张三', age: 25 },
{ id: 2, name: '李四', age: 30 },
{ id: 3, name: '王五', age: 28 }
];
// 3. 带索引的列表
const items = ['项目A', '项目B', '项目C'];
return (
<div className="list-demo">
<h3>水果列表</h3>
<ul>
{fruits.map((fruit, index) => (
<li key={index}>{fruit}</li>
))}
</ul>
<h3>用户列表</h3>
<ul>
{users.map(user => (
<li key={user.id}>
{user.name} - {user.age}岁
</li>
))}
</ul>
<h3>带索引的列表</h3>
<ul>
{items.map((item, index) => (
<li key={index}>
第{index + 1}项: {item}
</li>
))}
</ul>
</div>
);
}
4.2 过滤和排序
function FilterSortDemo() {
const [filter, setFilter] = useState('all');
const [sortBy, setSortBy] = useState('name');
const products = [
{ id: 1, name: '苹果', category: '水果', price: 10, inStock: true },
{ id: 2, name: '香蕉', category: '水果', price: 8, inStock: true },
{ id: 3, name: '白菜', category: '蔬菜', price: 5, inStock: false },
{ id: 4, name: '番茄', category: '蔬菜', price: 6, inStock: true },
{ id: 5, name: '橙子', category: '水果', price: 12, inStock: true }
];
// 过滤
const filteredProducts = products.filter(product => {
if (filter === 'inStock') return product.inStock;
if (filter === 'outOfStock') return !product.inStock;
return true;
});
// 排序
const sortedProducts = [...filteredProducts].sort((a, b) => {
if (sortBy === 'name') return a.name.localeCompare(b.name);
if (sortBy === 'price') return a.price - b.price;
return 0;
});
return (
<div className="filter-sort-demo">
<div className="controls">
<select value={filter} onChange={(e) => setFilter(e.target.value)}>
<option value="all">全部</option>
<option value="inStock">有货</option>
<option value="outOfStock">缺货</option>
</select>
<select value={sortBy} onChange={(e) => setSortBy(e.target.value)}>
<option value="name">按名称排序</option>
<option value="price">按价格排序</option>
</select>
</div>
<ul>
{sortedProducts.map(product => (
<li key={product.id} className={!product.inStock ? 'out-of-stock' : ''}>
<span>{product.name}</span>
<span>¥{product.price}</span>
<span>{product.inStock ? '有货' : '缺货'}</span>
</li>
))}
</ul>
</div>
);
}
4.3 分组渲染
function GroupRenderDemo() {
const groupedData = [
{ category: '水果', items: ['苹果', '香蕉', '橙子'] },
{ category: '蔬菜', items: ['白菜', '萝卜', '番茄'] },
{ category: '饮料', items: ['可乐', '雪碧', '果汁'] }
];
// 嵌套列表
const nestedData = [
{ id: 1, name: '组1', children: [
{ id: 11, name: '子项1' },
{ id: 12, name: '子项2' }
]},
{ id: 2, name: '组2', children: [
{ id: 21, name: '子项3' },
{ id: 22, name: '子项4' }
]}
];
// 树形结构组件
const TreeNode = ({ node }) => {
const [expanded, setExpanded] = useState(false);
return (
<div className="tree-node">
<div onClick={() => setExpanded(!expanded)} className="node-header">
{node.children && (
<span className="toggle">{expanded ? '▼' : '▶'}</span>
)}
{node.name}
</div>
{expanded && node.children && (
<div className="node-children">
{node.children.map(child => (
<TreeNode key={child.id} node={child} />
))}
</div>
)}
</div>
);
};
return (
<div className="group-demo">
<h3>分组列表</h3>
{groupedData.map((group, idx) => (
<div key={idx} className="group">
<h4>{group.category}</h4>
<ul>
{group.items.map((item, i) => (
<li key={i}>{item}</li>
))}
</ul>
</div>
))}
<h3>树形结构</h3>
{nestedData.map(node => (
<TreeNode key={node.id} node={node} />
))}
</div>
);
}
五、Key属性详解
5.1 Key的重要性
/**
* Key的作用:
* 1. 帮助React识别哪些元素发生了变化
* 2. 用于列表渲染时的性能优化
* 3. 保持组件状态在重新排序时的稳定性
*/
function KeyImportanceDemo() {
const [items, setItems] = useState([
{ id: 1, text: '学习React', completed: false },
{ id: 2, text: '掌握JSX', completed: false },
{ id: 3, text: '构建项目', completed: false }
]);
// 正确使用Key
const correctList = items.map(item => (
<li key={item.id}>{item.text}</li>
));
// 错误使用Key(使用索引)
const incorrectList = items.map((item, index) => (
<li key={index}>{item.text}</li> // 不推荐
));
// 添加新项(演示Key的作用)
const addItem = () => {
const newItem = {
id: Date.now(),
text: `新任务 ${items.length + 1}`,
completed: false
};
setItems([newItem, ...items]);
};
const removeItem = (id) => {
setItems(items.filter(item => item.id !== id));
};
return (
<div className="key-demo">
<button onClick={addItem}>添加任务</button>
<ul>
{items.map(item => (
<li key={item.id}>
<span>{item.text}</span>
<button onClick={() => removeItem(item.id)}>删除</button>
</li>
))}
</ul>
</div>
);
}
5.2 Key的最佳实践
/**
* Key的选择原则:
* 1. 使用数据的唯一ID作为key
* 2. 避免使用数组索引作为key
* 3. 避免使用随机数作为key
* 4. Key应该在兄弟节点间唯一
*/
function KeyBestPractices() {
// ✅ 好的Key(使用唯一ID)
const goodKeyList = users.map(user => (
<li key={user.id}>{user.name}</li>
));
// ✅ 好的Key(使用组合ID)
const compositeKeyList = items.map(item => (
<li key={`${item.type}-${item.id}`}>{item.name}</li>
));
// ❌ 坏的Key(使用索引)
const badKeyList = items.map((item, index) => (
<li key={index}>{item.name}</li> // 当列表顺序变化时会有问题
));
// ❌ 坏的Key(使用随机数)
const randomKeyList = items.map(item => (
<li key={Math.random()}>{item.name}</li> // 每次渲染都会变化
));
return null;
}
六、样式处理
6.1 行内样式
function InlineStyleDemo() {
// 基础行内样式
const divStyle = {
backgroundColor: '#f0f0f0',
padding: '20px',
borderRadius: '8px',
color: '#333'
};
// 动态样式
const [isActive, setIsActive] = useState(false);
const buttonStyle = {
backgroundColor: isActive ? '#4CAF50' : '#ccc',
color: 'white',
padding: '10px 20px',
border: 'none',
borderRadius: '4px',
cursor: 'pointer'
};
// 条件样式
const [theme, setTheme] = useState('light');
const themeStyles = {
light: { backgroundColor: '#fff', color: '#333' },
dark: { backgroundColor: '#333', color: '#fff' }
};
return (
<div style={themeStyles[theme]}>
<div style={divStyle}>
<h2 style={{ fontSize: '24px', marginBottom: '10px' }}>
行内样式示例
</h2>
<button
style={buttonStyle}
onClick={() => setIsActive(!isActive)}
>
{isActive ? '激活' : '未激活'}
</button>
<button
style={{ marginLeft: '10px' }}
onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}
>
切换主题
</button>
</div>
</div>
);
}
6.2 CSS模块
/* Button.module.css */
.button {
padding: 10px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
transition: all 0.3s ease;
}
.primary {
background-color: #4CAF50;
color: white;
}
.primary:hover {
background-color: #45a049;
}
.secondary {
background-color: #f0f0f0;
color: #333;
}
.large {
padding: 15px 30px;
font-size: 18px;
}
.small {
padding: 5px 10px;
font-size: 12px;
}
// Button.jsx
import styles from './Button.module.css';
function Button({ variant = 'primary', size = 'medium', children, onClick }) {
const sizeClass = {
small: styles.small,
medium: '',
large: styles.large
}[size];
return (
<button
className={`
${styles.button}
${styles[variant]}
${sizeClass}
`.trim()}
onClick={onClick}
>
{children}
</button>
);
}
6.3 CSS-in-JS (Styled Components)
import styled from 'styled-components';
// 定义样式组件
const StyledCard = styled.div`
background-color: ${props => props.theme === 'dark' ? '#333' : '#fff'};
border-radius: 8px;
padding: 20px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
transition: all 0.3s ease;
&:hover {
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
}
`;
const StyledTitle = styled.h2`
color: ${props => props.color || '#333'};
font-size: ${props => props.size || '24px'};
margin-bottom: 10px;
`;
const StyledButton = styled.button`
background-color: ${props => props.variant === 'primary' ? '#4CAF50' : '#ccc'};
color: white;
padding: ${props => props.size === 'large' ? '12px 24px' : '8px 16px'};
border: none;
border-radius: 4px;
cursor: pointer;
&:hover {
opacity: 0.9;
}
&:disabled {
opacity: 0.5;
cursor: not-allowed;
}
`;
// 使用样式组件
function StyledComponentsDemo() {
const [theme, setTheme] = useState('light');
return (
<StyledCard theme={theme}>
<StyledTitle color="#4CAF50" size="28px">
CSS-in-JS示例
</StyledTitle>
<StyledTitle>普通标题</StyledTitle>
<div>
<StyledButton variant="primary" size="large">
主要按钮
</StyledButton>
<StyledButton variant="secondary" style={{ marginLeft: '10px' }}>
次要按钮
</StyledButton>
</div>
<button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
切换主题
</button>
</StyledCard>
);
}
6.4 动态样式
function DynamicStyleDemo() {
const [progress, setProgress] = useState(0);
const [isVisible, setIsVisible] = useState(true);
const [activeIndex, setActiveIndex] = useState(0);
// 动态计算样式
const progressBarStyle = {
width: `${progress}%`,
height: '100%',
backgroundColor: progress < 30 ? '#f44336' : progress < 70 ? '#ff9800' : '#4CAF50',
transition: 'width 0.3s ease'
};
// 动态类名
const getTabClassName = (index) => {
return `tab ${activeIndex === index ? 'active' : ''}`;
};
// CSS变量
const cssVariableStyle = {
'--primary-color': '#4CAF50',
'--secondary-color': '#ff9800',
'--font-size': '16px'
};
return (
<div className="dynamic-style-demo" style={cssVariableStyle}>
{/* 动态宽度 */}
<div className="progress-container">
<div style={progressBarStyle} />
</div>
<button onClick={() => setProgress(p => Math.min(100, p + 10))}>
增加进度
</button>
{/* 动态显示/隐藏 */}
<div style={{ display: isVisible ? 'block' : 'none' }}>
可见内容
</div>
<button onClick={() => setIsVisible(!isVisible)}>
切换显示
</button>
{/* 动态类名 */}
<div className="tabs">
{['标签1', '标签2', '标签3'].map((tab, index) => (
<button
key={index}
className={getTabClassName(index)}
onClick={() => setActiveIndex(index)}
>
{tab}
</button>
))}
</div>
</div>
);
}
七、常见陷阱与注意事项
7.1 常见错误
/**
* JSX常见陷阱
*/
function CommonPitfalls() {
// ❌ 错误:忘记闭合标签
// <img src="image.jpg">
// ✅ 正确:自闭合标签
const correctImg = <img src="image.jpg" alt="描述" />;
// ❌ 错误:使用class而不是className
// <div class="container">内容</div>
// ✅ 正确:使用className
const correctDiv = <div className="container">内容</div>;
// ❌ 错误:使用for而不是htmlFor
// <label for="input">标签</label>
// ✅ 正确:使用htmlFor
const correctLabel = <label htmlFor="input">标签</label>;
// ❌ 错误:在JSX中使用if语句
// const element = if (condition) { ... }
// ✅ 正确:使用三元运算符或&&
const correctCondition = condition ? <div>显示</div> : null;
// ❌ 错误:直接在JSX中使用对象
// <div>{{ name: 'React' }}</div>
// ✅ 正确:对象需要先转换为字符串
const user = { name: 'React' };
const correctObject = <div>{JSON.stringify(user)}</div>;
return null;
}
7.2 性能注意事项
/**
* JSX性能优化
*/
function PerformanceTips() {
// ✅ 好的做法:在外部定义内联函数
const handleClick = useCallback(() => {
console.log('clicked');
}, []);
// ❌ 不好的做法:每次渲染都创建新函数
const badClick = () => console.log('clicked'); // 但可以配合useCallback
// ✅ 好的做法:使用useMemo缓存计算结果
const expensiveValue = useMemo(() => {
return heavyComputation(data);
}, [data]);
// ✅ 好的做法:条件渲染时避免创建不必要的元素
const conditionalElement = isVisible && <div>可见内容</div>;
// ✅ 好的做法:列表渲染使用稳定的key
const list = items.map(item => (
<ListItem key={item.id} item={item} />
));
return (
<div>
<button onClick={handleClick}>点击</button>
{conditionalElement}
{list}
</div>
);
}
八、总结
8.1 知识点回顾
| 知识点 | 说明 | 重要程度 |
|---|---|---|
| JSX基础语法 | 嵌入表达式、属性绑定 | ⭐⭐⭐⭐⭐ |
| 条件渲染 | if、三元、&&、switch | ⭐⭐⭐⭐⭐ |
| 列表渲染 | map、key、过滤、排序 | ⭐⭐⭐⭐⭐ |
| 样式处理 | 行内样式、CSS模块、CSS-in-JS | ⭐⭐⭐⭐ |
| 常见陷阱 | class vs className、标签闭合 | ⭐⭐⭐⭐ |
8.2 练习任务
// 练习1:创建一个产品卡片组件,包含条件渲染
// 要求:显示产品信息,根据库存状态显示不同样式
function ProductCard({ product }) {
// 实现代码
return <div>...</div>;
}
// 练习2:创建一个可排序和过滤的表格组件
// 要求:支持列排序、关键字过滤
function DataTable({ data, columns }) {
// 实现代码
return <table>...</table>;
}
// 练习3:创建一个可展开的树形菜单组件
// 要求:支持嵌套结构、展开/收起
function TreeMenu({ data }) {
// 实现代码
return <div>...</div>;
}
8.3 下一节预告
下一篇将学习 组件与Props,内容包括:
- 函数组件 vs 类组件
- Props传递与类型检查
- 组件组合模式
- 高阶组件
- Render Props
更多推荐


所有评论(0)