JSX 渲染文章列表
本章你将学会用 JSX 表达式把 JS 数据渲染成界面,让博客首页展示文章卡片列表。
{} 在 JSX 中嵌入表达式
在 JSX 中,花括号 {} 是进入 JavaScript 世界的入口。
任何 JS 表达式都可以放在花括号里:变量、运算、函数调用、三元表达式等。
实例
function HomePage() {
const blogTitle = 'RUNOOB 前端笔记'
const author = '小明'
return (
<div>
<h1>{blogTitle}</h1>
<p>作者:{author}</p>
<p>当前时间:{new Date().toLocaleDateString()}</p>
<p>欢迎语:{author ? `你好,${author}` : '欢迎访客'}</p>
</div>
)
}
const blogTitle = 'RUNOOB 前端笔记'
const author = '小明'
return (
<div>
<h1>{blogTitle}</h1>
<p>作者:{author}</p>
<p>当前时间:{new Date().toLocaleDateString()}</p>
<p>欢迎语:{author ? `你好,${author}` : '欢迎访客'}</p>
</div>
)
}
页面上会渲染出:
RUNOOB 前端笔记 作者:小明 当前时间:2024/5/15 欢迎语:你好,小明
花括号里只能放表达式(能算出值的代码),不能放语句。比如
{if (condition) ...}不合法,但{condition ? 'A' : 'B'}合法。
属性绑定
HTML 属性在 JSX 中需要使用花括号动态绑定。
实例
function ArticleCard() {
const coverUrl = '/images/react-cover.png'
const linkUrl = '/post/1'
return (
<div>
{/* 动态属性:花括号绑定 JS 变量 */}
<img src={coverUrl} alt="文章封面" />
<a href={linkUrl}>阅读全文</a>
{/* 注意:class 要写成 className */}
<div className="card active">卡片内容</div>
{/* style 接受一个对象,属性名用驼峰式 */}
<div style={{ color: '#42b883', fontSize: '16px' }}>
绿色文字
</div>
</div>
)
}
const coverUrl = '/images/react-cover.png'
const linkUrl = '/post/1'
return (
<div>
{/* 动态属性:花括号绑定 JS 变量 */}
<img src={coverUrl} alt="文章封面" />
<a href={linkUrl}>阅读全文</a>
{/* 注意:class 要写成 className */}
<div className="card active">卡片内容</div>
{/* style 接受一个对象,属性名用驼峰式 */}
<div style={{ color: '#42b883', fontSize: '16px' }}>
绿色文字
</div>
</div>
)
}
| HTML | JSX | 说明 |
|---|---|---|
class="btn" | className="btn" | class 是 JS 保留字 |
style="color: red" | style={{ color: 'red' }} | 接收对象,属性名驼峰式 |
src="xxx" | src={variable} | 动态值用花括号 |
onclick="fn()" | onClick={fn} | 事件名驼峰式,传函数引用 |
列表渲染:array.map()
React 没有像 Vue v-for 那样的指令。
在 JSX 中渲染列表,直接用 JavaScript 的 array.map() 方法。
实例
function ArticleList() {
// 用 JS 对象数组模拟文章数据
const articles = [
{
id: 1,
title: 'React 入门完全指南',
summary: '从零开始学习 React Hooks,涵盖 useState、useEffect 等核心概念。',
date: '2024-05-10',
category: 'React',
cover: '/images/react.png'
},
{
id: 2,
title: 'JavaScript 异步编程详解',
summary: '一文搞懂 Promise、async/await、事件循环与微任务队列。',
date: '2024-05-08',
category: 'JavaScript',
cover: '/images/js.png'
},
{
id: 3,
title: 'CSS Grid 布局实战',
summary: '用 CSS Grid 轻松实现复杂的响应式布局。',
date: '2024-05-05',
category: 'CSS',
cover: '/images/css.png'
}
]
return (
<div className="article-list">
{/* map 遍历数组,每条数据返回一段 JSX */}
{articles.map(article => (
<div key={article.id} className="card">
<img src={article.cover} alt={article.title} />
<div className="card-body">
<span className="category">{article.category}</span>
<h3>{article.title}</h3>
<p>{article.summary}</p>
<span className="date">{article.date}</span>
</div>
</div>
))}
</div>
)
}
// 用 JS 对象数组模拟文章数据
const articles = [
{
id: 1,
title: 'React 入门完全指南',
summary: '从零开始学习 React Hooks,涵盖 useState、useEffect 等核心概念。',
date: '2024-05-10',
category: 'React',
cover: '/images/react.png'
},
{
id: 2,
title: 'JavaScript 异步编程详解',
summary: '一文搞懂 Promise、async/await、事件循环与微任务队列。',
date: '2024-05-08',
category: 'JavaScript',
cover: '/images/js.png'
},
{
id: 3,
title: 'CSS Grid 布局实战',
summary: '用 CSS Grid 轻松实现复杂的响应式布局。',
date: '2024-05-05',
category: 'CSS',
cover: '/images/css.png'
}
]
return (
<div className="article-list">
{/* map 遍历数组,每条数据返回一段 JSX */}
{articles.map(article => (
<div key={article.id} className="card">
<img src={article.cover} alt={article.title} />
<div className="card-body">
<span className="category">{article.category}</span>
<h3>{article.title}</h3>
<p>{article.summary}</p>
<span className="date">{article.date}</span>
</div>
</div>
))}
</div>
)
}
key 属性的重要性
key 是给列表中每个元素分配的唯一标识,帮助 React 识别哪些元素变化了。
没有 key 时,React 采用「就地更新」策略,可能导致状态错乱和性能下降。
| key 取值 | 后果 |
|---|---|
| 唯一的 id(推荐) | 正确追踪每个元素,高效 Diff |
| 数组下标 index | 插入/删除时可能造成输入框内容错乱 |
| 不写 key | 控制台警告,性能差 |
始终给 map 返回的 JSX 加一个唯一且稳定的 key。不要用 index 作为 key,尤其是列表项可能增删排序时。
条件渲染
React 中没有 v-if,但有三种方式实现条件渲染。
实例
function ArticlePage() {
const articles = []
const isLoading = false
return (
<div>
{/* 方式一:三元表达式 — 适合二选一 */}
{isLoading ? (
<p>加载中,请稍候...</p>
) : (
<p>加载完成!</p>
)}
{/* 方式二:&& 短路 — 适合「有条件就显示,没条件就不显示」 */}
{articles.length === 0 && <p>还没有文章,敬请期待。</p>}
{/* 方式三:if/else 在组件顶层 — 适合复杂多条件逻辑 */}
{articles.length > 0 ? (
<div>
{articles.map(a => <div key={a.id}>{a.title}</div>)}
</div>
) : (
<p>暂无数据</p>
)}
</div>
)
}
const articles = []
const isLoading = false
return (
<div>
{/* 方式一:三元表达式 — 适合二选一 */}
{isLoading ? (
<p>加载中,请稍候...</p>
) : (
<p>加载完成!</p>
)}
{/* 方式二:&& 短路 — 适合「有条件就显示,没条件就不显示」 */}
{articles.length === 0 && <p>还没有文章,敬请期待。</p>}
{/* 方式三:if/else 在组件顶层 — 适合复杂多条件逻辑 */}
{articles.length > 0 ? (
<div>
{articles.map(a => <div key={a.id}>{a.title}</div>)}
</div>
) : (
<p>暂无数据</p>
)}
</div>
)
}
三种条件渲染方式对比
| 方式 | 写法 | 适用场景 |
|---|---|---|
三元 ? : | {cond ? <A /> : <B />} | 二选一,必须返回点什么 |
短路 && | {cond && <A />} | 有就显示,没有就不显示 |
| 提前 if 判断 | 在 return 之前用变量存好 JSX | 条件分支复杂的情况 |
&&短路有一个陷阱:当条件是数字 0 时,会渲染出 "0"。建议&&左边始终是布尔值:{list.length > 0 && ...}而不是{list.length && ...}。
动手:完整的博客首页
实例
// 文件路径:src/App.jsx
import './App.css'
function App() {
const articles = [
{
id: 1, title: 'React 入门完全指南',
summary: '从零开始学习 React Hooks,涵盖 useState、useEffect 等核心概念。',
date: '2024-05-10', category: 'React', cover: '/images/react.png'
},
{
id: 2, title: 'JavaScript 异步编程详解',
summary: '一文搞懂 Promise、async/await、事件循环与微任务队列。',
date: '2024-05-08', category: 'JavaScript', cover: '/images/js.png'
},
{
id: 3, title: 'CSS Grid 布局实战',
summary: '用 CSS Grid 轻松实现复杂的响应式布局。',
date: '2024-05-05', category: 'CSS', cover: '/images/css.png'
}
]
return (
<div className="app">
<header className="navbar">
<h1 className="logo">RUNOOB Blog</h1>
<nav>
<a href="/">首页</a>
<a href="#">关于</a>
</nav>
</header>
<main className="container">
<h2 className="section-title">最新文章</h2>
{/* 条件渲染:有文章就显示,没文章就显示空态 */}
{articles.length === 0 ? (
<p className="empty-tip">还没有文章,敬请期待。</p>
) : (
<div className="article-grid">
{/* 列表渲染:map 遍历数组 */}
{articles.map(article => (
<div key={article.id} className="article-card">
<img src={article.cover} alt={article.title} className="card-cover" />
<div className="card-content">
<span className="card-category">{article.category}</span>
<h3 className="card-title">{article.title}</h3>
<p className="card-summary">{article.summary}</p>
<span className="card-date">{article.date}</span>
</div>
</div>
))}
</div>
)}
</main>
<footer className="footer">
<p>© 2024 RUNOOB Blog. Powered by React.</p>
</footer>
</div>
)
}
export default App
import './App.css'
function App() {
const articles = [
{
id: 1, title: 'React 入门完全指南',
summary: '从零开始学习 React Hooks,涵盖 useState、useEffect 等核心概念。',
date: '2024-05-10', category: 'React', cover: '/images/react.png'
},
{
id: 2, title: 'JavaScript 异步编程详解',
summary: '一文搞懂 Promise、async/await、事件循环与微任务队列。',
date: '2024-05-08', category: 'JavaScript', cover: '/images/js.png'
},
{
id: 3, title: 'CSS Grid 布局实战',
summary: '用 CSS Grid 轻松实现复杂的响应式布局。',
date: '2024-05-05', category: 'CSS', cover: '/images/css.png'
}
]
return (
<div className="app">
<header className="navbar">
<h1 className="logo">RUNOOB Blog</h1>
<nav>
<a href="/">首页</a>
<a href="#">关于</a>
</nav>
</header>
<main className="container">
<h2 className="section-title">最新文章</h2>
{/* 条件渲染:有文章就显示,没文章就显示空态 */}
{articles.length === 0 ? (
<p className="empty-tip">还没有文章,敬请期待。</p>
) : (
<div className="article-grid">
{/* 列表渲染:map 遍历数组 */}
{articles.map(article => (
<div key={article.id} className="article-card">
<img src={article.cover} alt={article.title} className="card-cover" />
<div className="card-content">
<span className="card-category">{article.category}</span>
<h3 className="card-title">{article.title}</h3>
<p className="card-summary">{article.summary}</p>
<span className="card-date">{article.date}</span>
</div>
</div>
))}
</div>
)}
</main>
<footer className="footer">
<p>© 2024 RUNOOB Blog. Powered by React.</p>
</footer>
</div>
)
}
export default App
实例
/* 文件路径:src/App.css 追加以下样式 */
.section-title {
font-size: 24px;
margin-bottom: 20px;
color: #333;
}
.article-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 24px;
}
.article-card {
background: #fff;
border-radius: 12px;
overflow: hidden;
box-shadow: 0 2px 12px rgba(0,0,0,0.08);
transition: transform 0.2s, box-shadow 0.2s;
cursor: pointer;
}
.article-card:hover {
transform: translateY(-4px);
box-shadow: 0 8px 24px rgba(0,0,0,0.12);
}
.card-cover {
width: 100%;
height: 180px;
object-fit: cover;
}
.card-content {
padding: 16px;
}
.card-category {
display: inline-block;
padding: 2px 10px;
background: #e3f2fd;
color: #1976d2;
border-radius: 12px;
font-size: 12px;
margin-bottom: 8px;
}
.card-title {
font-size: 18px;
margin-bottom: 8px;
color: #222;
}
.card-summary {
font-size: 14px;
color: #666;
line-height: 1.6;
margin-bottom: 12px;
}
.card-date {
font-size: 12px;
color: #999;
}
.empty-tip {
text-align: center;
color: #999;
padding: 60px 0;
font-size: 16px;
}
.section-title {
font-size: 24px;
margin-bottom: 20px;
color: #333;
}
.article-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 24px;
}
.article-card {
background: #fff;
border-radius: 12px;
overflow: hidden;
box-shadow: 0 2px 12px rgba(0,0,0,0.08);
transition: transform 0.2s, box-shadow 0.2s;
cursor: pointer;
}
.article-card:hover {
transform: translateY(-4px);
box-shadow: 0 8px 24px rgba(0,0,0,0.12);
}
.card-cover {
width: 100%;
height: 180px;
object-fit: cover;
}
.card-content {
padding: 16px;
}
.card-category {
display: inline-block;
padding: 2px 10px;
background: #e3f2fd;
color: #1976d2;
border-radius: 12px;
font-size: 12px;
margin-bottom: 8px;
}
.card-title {
font-size: 18px;
margin-bottom: 8px;
color: #222;
}
.card-summary {
font-size: 14px;
color: #666;
line-height: 1.6;
margin-bottom: 12px;
}
.card-date {
font-size: 12px;
color: #999;
}
.empty-tip {
text-align: center;
color: #999;
padding: 60px 0;
font-size: 16px;
}
本章小结
本章你学会了 JSX 中四个核心操作:{} 嵌入表达式、属性绑定(className / style)、map() 列表渲染 + key、条件渲染(三元 / 短路)。
至此,博客首页已经可以根据 JS 数据动态渲染文章卡片了。
