数据获取基础 #
一、数据获取概述 #
1.1 Astro 数据获取特点 #
Astro 在构建时获取数据,这意味着:
text
┌─────────────────────────────────────────────────────┐
│ Astro 数据获取流程 │
├─────────────────────────────────────────────────────┤
│ │
│ 构建时 │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ 数据源 │ → │ 获取数据 │ → │ 生成HTML │ │
│ └─────────┘ └─────────┘ └─────────┘ │
│ │
│ 运行时 │
│ ┌─────────┐ │
│ │ 静态HTML │ → 直接发送给用户 │
│ └─────────┘ │
│ │
└─────────────────────────────────────────────────────┘
1.2 数据获取位置 #
在 Astro 组件的 Frontmatter 中获取数据:
astro
---
// 在这里获取数据
const response = await fetch('https://api.example.com/data');
const data = await response.json();
---
<!-- 使用数据 -->
<div>{data.title}</div>
二、使用 Fetch API #
2.1 基本 Fetch #
astro
---
// src/pages/posts.astro
const response = await fetch('https://jsonplaceholder.typicode.com/posts');
const posts = await response.json();
---
<div>
{posts.map(post => (
<article key={post.id}>
<h2>{post.title}</h2>
<p>{post.body}</p>
</article>
))}
</div>
2.2 错误处理 #
astro
---
const response = await fetch('https://api.example.com/posts');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const posts = await response.json();
---
<div>
{posts.map(post => (
<article key={post.id}>
<h2>{post.title}</h2>
</article>
))}
</div>
2.3 带请求头的 Fetch #
astro
---
const response = await fetch('https://api.example.com/posts', {
headers: {
'Authorization': 'Bearer your-token',
'Content-Type': 'application/json',
},
});
const posts = await response.json();
---
<div>
{posts.map(post => (
<article key={post.id}>{post.title}</article>
))}
</div>
2.4 封装 Fetch 函数 #
astro
---
// src/utils/api.ts
interface FetchOptions {
headers?: Record<string, string>;
timeout?: number;
}
async function fetchData<T>(
url: string,
options: FetchOptions = {}
): Promise<T> {
const { headers = {}, timeout = 5000 } = options;
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeout);
try {
const response = await fetch(url, {
headers: {
'Content-Type': 'application/json',
...headers,
},
signal: controller.signal,
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json();
} finally {
clearTimeout(timeoutId);
}
}
// 使用
const posts = await fetchData<Post[]>('https://api.example.com/posts');
---
<div>
{posts.map(post => (
<article key={post.id}>{post.title}</article>
))}
</div>
三、本地 JSON 文件 #
3.1 读取本地 JSON #
json
// src/data/products.json
[
{ "id": 1, "name": "商品A", "price": 100 },
{ "id": 2, "name": "商品B", "price": 200 },
{ "id": 3, "name": "商品C", "price": 150 }
]
astro
---
// src/pages/products.astro
import products from '../data/products.json';
---
<div class="products">
{products.map(product => (
<div key={product.id} class="product">
<h3>{product.name}</h3>
<p>价格: ¥{product.price}</p>
</div>
))}
</div>
3.2 使用 Glob 导入多个文件 #
astro
---
// src/pages/blog/index.astro
const posts = await Astro.glob('../content/blog/*.md');
---
<div>
{posts.map(post => (
<article>
<h2>{post.frontmatter.title}</h2>
<p>{post.frontmatter.excerpt}</p>
</article>
))}
</div>
3.3 Glob 导入 JSON 文件 #
astro
---
// src/pages/products.astro
const products = await Astro.glob('../data/products/*.json');
---
<div>
{products.map(({ default: product }) => (
<div key={product.id}>
<h3>{product.name}</h3>
<p>{product.description}</p>
</div>
))}
</div>
四、环境变量 #
4.1 使用环境变量 #
bash
# .env
API_URL=https://api.example.com
API_KEY=your-api-key
astro
---
const apiUrl = import.meta.env.API_URL;
const apiKey = import.meta.env.API_KEY;
const response = await fetch(`${apiUrl}/posts`, {
headers: {
'Authorization': `Bearer ${apiKey}`,
},
});
const posts = await response.json();
---
<div>
{posts.map(post => (
<article key={post.id}>{post.title}</article>
))}
</div>
4.2 环境变量类型定义 #
typescript
// src/env.d.ts
interface ImportMetaEnv {
readonly API_URL: string;
readonly API_KEY: string;
}
interface ImportMeta {
readonly env: ImportMetaEnv;
}
五、数据缓存 #
5.1 构建时缓存 #
Astro 默认在构建时获取数据,结果会被缓存:
astro
---
// 这个请求只在构建时执行一次
const posts = await fetch('https://api.example.com/posts').then(r => r.json());
---
<div>
{posts.map(post => (
<article key={post.id}>{post.title}</article>
))}
</div>
5.2 自定义缓存函数 #
astro
---
// src/utils/cache.ts
const cache = new Map<string, any>();
async function cachedFetch<T>(url: string, ttl = 3600000): Promise<T> {
const cached = cache.get(url);
if (cached && Date.now() - cached.timestamp < ttl) {
return cached.data;
}
const response = await fetch(url);
const data = await response.json();
cache.set(url, {
data,
timestamp: Date.now(),
});
return data;
}
// 使用
const posts = await cachedFetch<Post[]>('https://api.example.com/posts');
---
六、并行数据获取 #
6.1 Promise.all #
astro
---
const [posts, users, comments] = await Promise.all([
fetch('https://api.example.com/posts').then(r => r.json()),
fetch('https://api.example.com/users').then(r => r.json()),
fetch('https://api.example.com/comments').then(r => r.json()),
]);
---
<div>
<section>
<h2>文章 ({posts.length})</h2>
{posts.slice(0, 5).map(post => <p key={post.id}>{post.title}</p>)}
</section>
<section>
<h2>用户 ({users.length})</h2>
{users.slice(0, 5).map(user => <p key={user.id}>{user.name}</p>)}
</section>
</div>
6.2 封装并行请求 #
astro
---
interface Endpoints {
posts: string;
users: string;
comments: string;
}
async function fetchMultiple<T extends Record<string, string>>(
endpoints: T
): Promise<{ [K in keyof T]: any }> {
const entries = Object.entries(endpoints) as [keyof T, string][];
const responses = await Promise.all(
entries.map(async ([key, url]) => {
const response = await fetch(url);
return [key, await response.json()] as const;
})
);
return Object.fromEntries(responses) as { [K in keyof T]: any };
}
// 使用
const data = await fetchMultiple({
posts: 'https://api.example.com/posts',
users: 'https://api.example.com/users',
});
console.log(data.posts); // 文章数据
console.log(data.users); // 用户数据
---
七、GraphQL API #
7.1 查询 GraphQL #
astro
---
const query = `
query {
posts {
id
title
excerpt
author {
name
}
}
}
`;
const response = await fetch('https://api.example.com/graphql', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ query }),
});
const { data } = await response.json();
---
<div>
{data.posts.map(post => (
<article key={post.id}>
<h2>{post.title}</h2>
<p>作者: {post.author.name}</p>
<p>{post.excerpt}</p>
</article>
))}
</div>
7.2 封装 GraphQL 客户端 #
astro
---
// src/utils/graphql.ts
async function graphql<T>(
endpoint: string,
query: string,
variables?: Record<string, any>
): Promise<T> {
const response = await fetch(endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ query, variables }),
});
const { data, errors } = await response.json();
if (errors) {
throw new Error(errors[0].message);
}
return data;
}
// 使用
const data = await graphql<{ posts: Post[] }>(
'https://api.example.com/graphql',
`query { posts { id title } }`
);
---
八、数据转换 #
8.1 数据过滤 #
astro
---
const posts = await fetch('https://api.example.com/posts')
.then(r => r.json())
.then(data => data.filter(post => post.published));
---
<div>
{posts.map(post => (
<article key={post.id}>{post.title}</article>
))}
</div>
8.2 数据排序 #
astro
---
const posts = await fetch('https://api.example.com/posts')
.then(r => r.json())
.then(data => data.sort((a, b) =>
new Date(b.date).getTime() - new Date(a.date).getTime()
));
---
<div>
{posts.map(post => (
<article key={post.id}>
<time>{post.date}</time>
<h2>{post.title}</h2>
</article>
))}
</div>
8.3 数据分组 #
astro
---
const posts = await fetch('https://api.example.com/posts').then(r => r.json());
const groupedPosts = posts.reduce((acc, post) => {
const year = new Date(post.date).getFullYear();
if (!acc[year]) acc[year] = [];
acc[year].push(post);
return acc;
}, {});
---
<div>
{Object.entries(groupedPosts).map(([year, yearPosts]) => (
<section key={year}>
<h2>{year}年</h2>
{yearPosts.map(post => (
<article key={post.id}>{post.title}</article>
))}
</section>
))}
</div>
九、最佳实践 #
9.1 错误处理 #
astro
---
let posts = [];
let error = null;
try {
const response = await fetch('https://api.example.com/posts');
if (!response.ok) throw new Error('Failed to fetch');
posts = await response.json();
} catch (e) {
error = e.message;
}
---
{error && <div class="error">加载失败: {error}</div>}
{posts.length > 0 && (
<div>
{posts.map(post => (
<article key={post.id}>{post.title}</article>
))}
</div>
)}
9.2 空数据处理 #
astro
---
const posts = await fetch('https://api.example.com/posts')
.then(r => r.json())
.catch(() => []);
---
{posts.length === 0 ? (
<div class="empty">暂无文章</div>
) : (
<div>
{posts.map(post => (
<article key={post.id}>{post.title}</article>
))}
</div>
)}
9.3 类型安全 #
typescript
// src/types/post.ts
interface Post {
id: number;
title: string;
body: string;
userId: number;
}
interface User {
id: number;
name: string;
email: string;
}
astro
---
import type { Post, User } from '../types';
const posts: Post[] = await fetch('https://api.example.com/posts')
.then(r => r.json());
---
<div>
{posts.map((post: Post) => (
<article key={post.id}>
<h2>{post.title}</h2>
<p>{post.body}</p>
</article>
))}
</div>
十、总结 #
数据获取核心要点:
text
┌─────────────────────────────────────────────────────┐
│ 数据获取要点 │
├─────────────────────────────────────────────────────┤
│ │
│ 🌐 Fetch API 构建时获取远程数据 │
│ │
│ 📁 本地文件 导入 JSON/Markdown 文件 │
│ │
│ 📦 Glob 导入 批量导入多个文件 │
│ │
│ 🔐 环境变量 安全存储敏感配置 │
│ │
│ ⚡ 并行请求 Promise.all 提升效率 │
│ │
└─────────────────────────────────────────────────────┘
下一步,让我们学习 API集成,深入了解外部 API 的集成方法!
最后更新:2026-03-28