列表渲染 #

一、列表渲染基础 #

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