查询数据 #

一、基本查询 #

1.1 getCollection #

获取集合中的所有条目:

astro
---
import { getCollection } from 'astro:content';

const posts = await getCollection('blog');
---

<div>
  {posts.map(post => (
    <article key={post.slug}>
      <h2>{post.data.title}</h2>
    </article>
  ))}
</div>

1.2 getEntry #

获取单个条目:

astro
---
import { getEntry } from 'astro:content';

const post = await getEntry('blog', 'hello-world');
---

<article>
  <h1>{post.data.title}</h1>
  <p>{post.data.description}</p>
</article>

1.3 getEntries #

获取多个条目:

astro
---
import { getEntries } from 'astro:content';

const posts = await getEntries([
  { collection: 'blog', slug: 'hello-world' },
  { collection: 'blog', slug: 'astro-guide' },
]);
---

<div>
  {posts.map(post => (
    <article key={post.slug}>{post.data.title}</article>
  ))}
</div>

二、过滤查询 #

2.1 基本过滤 #

astro
---
import { getCollection } from 'astro:content';

const posts = await getCollection('blog', ({ data }) => {
  return !data.draft;
});
---

<div>
  {posts.map(post => (
    <article key={post.slug}>{post.data.title}</article>
  ))}
</div>

2.2 多条件过滤 #

astro
---
const posts = await getCollection('blog', ({ data }) => {
  return !data.draft && data.featured;
});
---

<div>
  {posts.map(post => (
    <article key={post.slug}>
      <span class="badge">精选</span>
      <h2>{post.data.title}</h2>
    </article>
  ))}
</div>

2.3 日期过滤 #

astro
---
const now = new Date();
const currentYear = now.getFullYear();

const posts = await getCollection('blog', ({ data }) => {
  const postYear = data.date.getFullYear();
  return !data.draft && postYear === currentYear;
});
---

<div>
  <h2>{currentYear}年的文章</h2>
  {posts.map(post => (
    <article key={post.slug}>{post.data.title}</article>
  ))}
</div>

2.4 标签过滤 #

astro
---
const tag = Astro.url.searchParams.get('tag') || '';

const posts = await getCollection('blog', ({ data }) => {
  if (!tag) return !data.draft;
  return !data.draft && data.tags.includes(tag);
});
---

<div>
  {tag && <p>标签: {tag}</p>}
  {posts.map(post => (
    <article key={post.slug}>{post.data.title}</article>
  ))}
</div>

2.5 分类过滤 #

astro
---
const category = Astro.params.category;

const posts = await getCollection('blog', ({ data }) => {
  return !data.draft && data.categories.includes(category);
});
---

<div>
  <h1>分类: {category}</h1>
  {posts.map(post => (
    <article key={post.slug}>{post.data.title}</article>
  ))}
</div>

三、排序 #

3.1 按日期排序 #

astro
---
import { getCollection } from 'astro:content';

const posts = (await getCollection('blog'))
  .filter(post => !post.data.draft)
  .sort((a, b) => b.data.date.valueOf() - a.data.date.valueOf());
---

<div>
  {posts.map(post => (
    <article key={post.slug}>
      <time>{post.data.date.toLocaleDateString('zh-CN')}</time>
      <h2>{post.data.title}</h2>
    </article>
  ))}
</div>

3.2 按标题排序 #

astro
---
const posts = (await getCollection('blog'))
  .filter(post => !post.data.draft)
  .sort((a, b) => a.data.title.localeCompare(b.data.title, 'zh-CN'));
---

<div>
  {posts.map(post => (
    <article key={post.slug}>{post.data.title}</article>
  ))}
</div>

3.3 按自定义字段排序 #

astro
---
const docs = (await getCollection('docs'))
  .sort((a, b) => a.data.order - b.data.order);
---

<div>
  {docs.map(doc => (
    <article key={doc.slug}>
      {doc.data.order}. {doc.data.title}
    </article>
  ))}
</div>

3.4 多字段排序 #

astro
---
const posts = (await getCollection('blog'))
  .filter(post => !post.data.draft)
  .sort((a, b) => {
    if (a.data.featured !== b.data.featured) {
      return b.data.featured ? 1 : -1;
    }
    return b.data.date.valueOf() - a.data.date.valueOf();
  });
---

<div>
  {posts.map(post => (
    <article key={post.slug}>
      {post.data.featured && <span class="badge">精选</span>}
      <h2>{post.data.title}</h2>
    </article>
  ))}
</div>

四、分页 #

4.1 手动分页 #

astro
---
import { getCollection } from 'astro:content';

const page = parseInt(Astro.url.searchParams.get('page') || '1');
const pageSize = 10;

const allPosts = (await getCollection('blog'))
  .filter(post => !post.data.draft)
  .sort((a, b) => b.data.date.valueOf() - a.data.date.valueOf());

const totalPages = Math.ceil(allPosts.length / pageSize);
const startIndex = (page - 1) * pageSize;
const posts = allPosts.slice(startIndex, startIndex + pageSize);
---

<div>
  {posts.map(post => (
    <article key={post.slug}>{post.data.title}</article>
  ))}
  
  <nav class="pagination">
    {page > 1 && (
      <a href={`?page=${page - 1}`}>上一页</a>
    )}
    <span>第 {page} / {totalPages} 页</span>
    {page < totalPages && (
      <a href={`?page=${page + 1}`}>下一页</a>
    )}
  </nav>
</div>

4.2 使用 Astro 分页 #

astro
---
// src/pages/blog/[page].astro
export async function getStaticPaths({ paginate }) {
  const posts = (await getCollection('blog'))
    .filter(post => !post.data.draft)
    .sort((a, b) => b.data.date.valueOf() - a.data.date.valueOf());
  
  return paginate(posts, { pageSize: 10 });
}

const { page } = Astro.props;
---

<div>
  {page.data.map(post => (
    <article key={post.slug}>
      <h2>{post.data.title}</h2>
      <time>{post.data.date.toLocaleDateString('zh-CN')}</time>
    </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>

五、分组 #

5.1 按年份分组 #

astro
---
import { getCollection } from 'astro:content';

const posts = (await getCollection('blog'))
  .filter(post => !post.data.draft)
  .sort((a, b) => b.data.date.valueOf() - a.data.date.valueOf());

const groupedByYear = posts.reduce((acc, post) => {
  const year = post.data.date.getFullYear();
  if (!acc[year]) acc[year] = [];
  acc[year].push(post);
  return acc;
}, {} as Record<number, typeof posts>);
---

<div>
  {Object.entries(groupedByYear)
    .sort(([a], [b]) => Number(b) - Number(a))
    .map(([year, yearPosts]) => (
      <section key={year}>
        <h2>{year}年</h2>
        {yearPosts.map(post => (
          <article key={post.slug}>
            <h3>{post.data.title}</h3>
          </article>
        ))}
      </section>
    ))
  }
</div>

5.2 按分类分组 #

astro
---
const posts = await getCollection('blog');

const groupedByCategory = posts.reduce((acc, post) => {
  post.data.categories.forEach(category => {
    if (!acc[category]) acc[category] = [];
    acc[category].push(post);
  });
  return acc;
}, {} as Record<string, typeof posts>);
---

<div>
  {Object.entries(groupedByCategory).map(([category, categoryPosts]) => (
    <section key={category}>
      <h2>{category}</h2>
      {categoryPosts.map(post => (
        <article key={post.slug}>{post.data.title}</article>
      ))}
    </section>
  ))}
</div>

5.3 按标签分组 #

astro
---
const posts = await getCollection('blog');

const tags = posts.reduce((acc, post) => {
  post.data.tags.forEach(tag => {
    if (!acc[tag]) acc[tag] = 0;
    acc[tag]++;
  });
  return acc;
}, {} as Record<string, number>);
---

<div class="tags">
  {Object.entries(tags)
    .sort(([, a], [, b]) => b - a)
    .map(([tag, count]) => (
      <a href={`/tags/${tag}`} class="tag">
        {tag} ({count})
      </a>
    ))
  }
</div>

六、关联查询 #

6.1 作者关联 #

astro
---
import { getCollection, getEntry } from 'astro:content';

const posts = await getCollection('blog');

async function getPostWithAuthor(post) {
  const author = await getEntry('authors', post.data.author);
  return { post, author };
}

const postsWithAuthors = await Promise.all(
  posts.map(getPostWithAuthor)
);
---

<div>
  {postsWithAuthors.map(({ post, author }) => (
    <article key={post.slug}>
      <h2>{post.data.title}</h2>
      <p>作者: {author.data.name}</p>
    </article>
  ))}
</div>

6.2 相关文章 #

astro
---
import { getCollection, getEntry } from 'astro:content';

const currentPost = await getEntry('blog', Astro.params.slug);
const allPosts = await getCollection('blog');

const relatedPosts = allPosts
  .filter(post => 
    post.slug !== currentPost.slug &&
    !post.data.draft &&
    post.data.tags.some(tag => currentPost.data.tags.includes(tag))
  )
  .sort((a, b) => {
    const aCommonTags = a.data.tags.filter(t => currentPost.data.tags.includes(t)).length;
    const bCommonTags = b.data.tags.filter(t => currentPost.data.tags.includes(t)).length;
    return bCommonTags - aCommonTags;
  })
  .slice(0, 3);
---

<aside>
  <h3>相关文章</h3>
  {relatedPosts.map(post => (
    <a key={post.slug} href={`/blog/${post.slug}`}>
      {post.data.title}
    </a>
  ))}
</aside>

七、搜索功能 #

7.1 标题搜索 #

astro
---
const query = Astro.url.searchParams.get('q')?.toLowerCase() || '';

const posts = (await getCollection('blog'))
  .filter(post => 
    !post.data.draft &&
    post.data.title.toLowerCase().includes(query)
  );
---

<div>
  <form>
    <input type="search" name="q" value={query} placeholder="搜索文章..." />
    <button type="submit">搜索</button>
  </form>
  
  {query && (
    <p>搜索 "{query}" 找到 {posts.length} 篇文章</p>
  )}
  
  {posts.map(post => (
    <article key={post.slug}>{post.data.title}</article>
  ))}
</div>

7.2 全文搜索 #

astro
---
const query = Astro.url.searchParams.get('q')?.toLowerCase() || '';

const posts = (await getCollection('blog'))
  .filter(post => {
    if (post.data.draft) return false;
    if (!query) return true;
    
    const searchContent = [
      post.data.title,
      post.data.description,
      post.body,
    ].join(' ').toLowerCase();
    
    return searchContent.includes(query);
  });
---

<div>
  {posts.map(post => (
    <article key={post.slug}>
      <h2>{post.data.title}</h2>
      <p>{post.data.description}</p>
    </article>
  ))}
</div>

八、缓存和性能 #

8.1 预加载数据 #

typescript
// src/lib/content.ts
import { getCollection } from 'astro:content';

let cachedPosts: Awaited<ReturnType<typeof getCollection>> | null = null;

export async function getPosts() {
  if (!cachedPosts) {
    cachedPosts = await getCollection('blog');
  }
  return cachedPosts;
}

8.2 使用预加载数据 #

astro
---
import { getPosts } from '../lib/content';

const posts = await getPosts();
---

<div>
  {posts.map(post => (
    <article key={post.slug}>{post.data.title}</article>
  ))}
</div>

九、总结 #

查询数据核心要点:

text
┌─────────────────────────────────────────────────────┐
│                 查询数据要点                         │
├─────────────────────────────────────────────────────┤
│                                                     │
│  📋 getCollection   获取集合所有条目                │
│                                                     │
│  📄 getEntry        获取单个条目                    │
│                                                     │
│  🔍 过滤            条件筛选数据                    │
│                                                     │
│  📊 排序            按字段排序                      │
│                                                     │
│  📖 分页            分页显示数据                    │
│                                                     │
│  📦 分组            按条件分组                      │
│                                                     │
│  🔗 关联            关联查询                        │
│                                                     │
└─────────────────────────────────────────────────────┘

下一步,让我们学习 内容渲染,掌握内容渲染技术!

最后更新:2026-03-28