列表渲染 #
一、列表渲染基础 #
1.1 使用 map 方法 #
在 Astro 中,使用 JavaScript 的 map 方法渲染列表:
astro
---
const items = ['苹果', '香蕉', '橙子'];
---
<ul>
{items.map(item => (
<li>{item}</li>
))}
</ul>
1.2 渲染对象数组 #
astro
---
const products = [
{ id: 1, name: '商品A', price: 100 },
{ id: 2, name: '商品B', price: 200 },
{ id: 3, name: '商品C', price: 150 }
];
---
<div class="products">
{products.map(product => (
<div class="product">
<h3>{product.name}</h3>
<p>价格: ¥{product.price}</p>
</div>
))}
</div>
二、Key 属性 #
2.1 为什么需要 Key? #
Key 帮助 Astro 识别列表中的元素,提高渲染效率:
astro
---
const items = [
{ id: 1, name: '项目一' },
{ id: 2, name: '项目二' },
{ id: 3, name: '项目三' }
];
---
<ul>
{items.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
2.2 Key 的选择 #
astro
---
const posts = [
{ id: 1, slug: 'hello-world', title: 'Hello World' },
{ id: 2, slug: 'astro-guide', title: 'Astro Guide' }
];
---
<!-- ✅ 好的做法:使用唯一 ID -->
{posts.map(post => (
<article key={post.id}>
<h2>{post.title}</h2>
</article>
))}
<!-- ✅ 也可以使用其他唯一值 -->
{posts.map(post => (
<article key={post.slug}>
<h2>{post.title}</h2>
</article>
))}
<!-- ❌ 不好的做法:使用索引作为 key -->
{posts.map((post, index) => (
<article key={index}>
<h2>{post.title}</h2>
</article>
))}
三、列表渲染技巧 #
3.1 带索引的渲染 #
astro
---
const colors = ['红色', '绿色', '蓝色', '黄色'];
---
<ol>
{colors.map((color, index) => (
<li>
{index + 1}. {color}
</li>
))}
</ol>
3.2 条件列表 #
astro
---
const items = [
{ id: 1, name: '项目A', active: true },
{ id: 2, name: '项目B', active: false },
{ id: 3, name: '项目C', active: true }
];
---
<ul>
{items.filter(item => item.active).map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
3.3 排序列表 #
astro
---
const products = [
{ id: 1, name: 'C产品', price: 300 },
{ id: 2, name: 'A产品', price: 100 },
{ id: 3, name: 'B产品', price: 200 }
];
const sortedProducts = [...products].sort((a, b) => a.price - b.price);
---
<div>
{sortedProducts.map(product => (
<div key={product.id}>
{product.name} - ¥{product.price}
</div>
))}
</div>
3.4 分组列表 #
astro
---
const items = [
{ id: 1, name: '苹果', category: '水果' },
{ id: 2, name: '胡萝卜', category: '蔬菜' },
{ id: 3, name: '香蕉', category: '水果' },
{ id: 4, name: '西红柿', category: '蔬菜' }
];
const grouped = items.reduce((acc, item) => {
if (!acc[item.category]) {
acc[item.category] = [];
}
acc[item.category].push(item);
return acc;
}, {});
---
<div>
{Object.entries(grouped).map(([category, categoryItems]) => (
<div key={category}>
<h2>{category}</h2>
<ul>
{categoryItems.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
</div>
))}
</div>
四、嵌套循环 #
4.1 基本嵌套 #
astro
---
const categories = [
{
name: '水果',
items: ['苹果', '香蕉', '橙子']
},
{
name: '蔬菜',
items: ['胡萝卜', '西红柿', '黄瓜']
}
];
---
<div>
{categories.map(category => (
<div key={category.name}>
<h2>{category.name}</h2>
<ul>
{category.items.map((item, index) => (
<li key={`${category.name}-${index}`}>{item}</li>
))}
</ul>
</div>
))}
</div>
4.2 矩阵渲染 #
astro
---
const matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
];
---
<table>
{matrix.map((row, rowIndex) => (
<tr key={rowIndex}>
{row.map((cell, colIndex) => (
<td key={colIndex}>{cell}</td>
))}
</tr>
))}
</table>
4.3 导航菜单 #
astro
---
const menu = [
{
label: '产品',
children: [
{ label: '产品A', href: '/products/a' },
{ label: '产品B', href: '/products/b' }
]
},
{
label: '服务',
children: [
{ label: '咨询', href: '/services/consulting' },
{ label: '开发', href: '/services/development' }
]
}
];
---
<nav>
<ul class="menu">
{menu.map(item => (
<li key={item.label} class="menu-item">
<span>{item.label}</span>
<ul class="submenu">
{item.children.map(child => (
<li key={child.label}>
<a href={child.href}>{child.label}</a>
</li>
))}
</ul>
</li>
))}
</ul>
</nav>
五、列表组件化 #
5.1 列表项组件 #
astro
---
// src/components/ProductCard.astro
interface Props {
product: {
id: number;
name: string;
price: number;
image: string;
};
}
const { product } = Astro.props;
---
<div class="product-card">
<img src={product.image} alt={product.name} />
<h3>{product.name}</h3>
<p class="price">¥{product.price}</p>
</div>
<style>
.product-card {
border: 1px solid #e5e7eb;
border-radius: 8px;
padding: 1rem;
}
.price {
color: #dc2626;
font-weight: bold;
}
</style>
5.2 使用列表项组件 #
astro
---
// src/pages/products.astro
import ProductCard from '../components/ProductCard.astro';
const products = [
{ id: 1, name: '商品A', price: 100, image: '/a.jpg' },
{ id: 2, name: '商品B', price: 200, image: '/b.jpg' },
{ id: 3, name: '商品C', price: 150, image: '/c.jpg' }
];
---
<div class="product-grid">
{products.map(product => (
<ProductCard key={product.id} product={product} />
))}
</div>
<style>
.product-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: 1.5rem;
}
</style>
六、分页列表 #
6.1 简单分页 #
astro
---
const allItems = Array.from({ length: 50 }, (_, i) => ({
id: i + 1,
name: `项目 ${i + 1}`
}));
const page = 1;
const pageSize = 10;
const startIndex = (page - 1) * pageSize;
const endIndex = startIndex + pageSize;
const pageItems = allItems.slice(startIndex, endIndex);
const totalPages = Math.ceil(allItems.length / pageSize);
---
<div>
<ul>
{pageItems.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
<div class="pagination">
{Array.from({ length: totalPages }, (_, i) => (
<a
key={i + 1}
href={`/page/${i + 1}`}
class={page === i + 1 ? 'active' : ''}
>
{i + 1}
</a>
))}
</div>
</div>
6.2 使用 Astro 分页 #
astro
---
// src/pages/blog/[page].astro
export async function getStaticPaths({ paginate }) {
const posts = await getAllPosts();
return paginate(posts, { pageSize: 10 });
}
const { page } = Astro.props;
---
<div>
{page.data.map(post => (
<article key={post.id}>
<h2>{post.title}</h2>
<p>{post.excerpt}</p>
</article>
))}
<nav class="pagination">
{page.url.prev && (
<a href={page.url.prev}>上一页</a>
)}
<span>第 {page.currentPage} / {page.lastPage} 页</span>
{page.url.next && (
<a href={page.url.next}>下一页</a>
)}
</nav>
</div>
七、实际应用示例 #
7.1 博客文章列表 #
astro
---
const posts = [
{
id: 1,
title: 'Astro 入门指南',
excerpt: '学习 Astro 的基础知识...',
date: '2024-01-15',
tags: ['Astro', '前端']
},
{
id: 2,
title: '现代 CSS 技巧',
excerpt: '探索 CSS 的新特性...',
date: '2024-01-10',
tags: ['CSS', '前端']
}
];
---
<div class="posts">
{posts.map(post => (
<article key={post.id} class="post">
<time datetime={post.date}>{post.date}</time>
<h2>{post.title}</h2>
<p>{post.excerpt}</p>
<div class="tags">
{post.tags.map(tag => (
<span key={tag} class="tag">{tag}</span>
))}
</div>
</article>
))}
</div>
7.2 表格数据 #
astro
---
const users = [
{ id: 1, name: '张三', email: 'zhang@example.com', role: '管理员' },
{ id: 2, name: '李四', email: 'li@example.com', role: '用户' },
{ id: 3, name: '王五', email: 'wang@example.com', role: '用户' }
];
---
<table class="data-table">
<thead>
<tr>
<th>ID</th>
<th>姓名</th>
<th>邮箱</th>
<th>角色</th>
</tr>
</thead>
<tbody>
{users.map(user => (
<tr key={user.id}>
<td>{user.id}</td>
<td>{user.name}</td>
<td>{user.email}</td>
<td>
<span class={`badge badge-${user.role === '管理员' ? 'admin' : 'user'}`}>
{user.role}
</span>
</td>
</tr>
))}
</tbody>
</table>
7.3 图库网格 #
astro
---
const images = [
{ id: 1, src: '/images/1.jpg', alt: '图片1', width: 400, height: 300 },
{ id: 2, src: '/images/2.jpg', alt: '图片2', width: 400, height: 300 },
{ id: 3, src: '/images/3.jpg', alt: '图片3', width: 400, height: 300 },
{ id: 4, src: '/images/4.jpg', alt: '图片4', width: 400, height: 300 }
];
---
<div class="gallery">
{images.map(image => (
<figure key={image.id} class="gallery-item">
<img
src={image.src}
alt={image.alt}
width={image.width}
height={image.height}
loading="lazy"
/>
<figcaption>{image.alt}</figcaption>
</figure>
))}
</div>
<style>
.gallery {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: 1rem;
}
.gallery-item img {
width: 100%;
height: auto;
border-radius: 8px;
}
</style>
八、性能优化 #
8.1 虚拟列表概念 #
对于大型列表,考虑分页或懒加载:
astro
---
const allItems = Array.from({ length: 1000 }, (_, i) => ({
id: i + 1,
name: `项目 ${i + 1}`
}));
// 只渲染前 20 个
const visibleItems = allItems.slice(0, 20);
---
<ul>
{visibleItems.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
<button data-load-more>加载更多</button>
8.2 避免重复计算 #
astro
---
// ✅ 好的做法:预先计算
const items = await getItems();
const filteredItems = items.filter(item => item.active);
const sortedItems = filteredItems.sort((a, b) => a.order - b.order);
---
{sortedItems.map(item => (
<div key={item.id}>{item.name}</div>
))}
---
// ❌ 不好的做法:每次渲染都计算
const items = await getItems();
---
{items.filter(item => item.active).sort((a, b) => a.order - b.order).map(item => (
<div key={item.id}>{item.name}</div>
))}
九、总结 #
列表渲染核心要点:
text
┌─────────────────────────────────────────────────────┐
│ 列表渲染要点 │
├─────────────────────────────────────────────────────┤
│ │
│ 📋 map 方法 使用 map 渲染列表 │
│ │
│ 🔑 Key 属性 为每个元素提供唯一标识 │
│ │
│ 🔄 嵌套循环 支持多层嵌套渲染 │
│ │
│ 📦 组件化 将列表项抽取为组件 │
│ │
│ 📄 分页 使用 paginate 处理分页 │
│ │
└─────────────────────────────────────────────────────┘
下一步,让我们学习 插槽,掌握组件内容分发技术!
最后更新:2026-03-28