Типизация getStaticProps
Допустим, мы создаем статическую страницу блога, где нам нужно выводить заголовок и описание каждого поста. Вот как это делается без типизации:
// pages/blog.tsx
export const getStaticProps = async () => {
const response = await fetch("https://jsonplaceholder.typicode.com/posts");
const posts = await response.json();
return {
props: {
posts, // Здесь пока нет типизации
},
};
};
const Blog = ({ posts }: { posts: any[] }) => {
return (
<div>
<h1>Блог</h1>
{posts.map((post) => (
<div key={post.id}>
<h2>{post.title}</h2>
<p>{post.body}</p>
</div>
))}
</div>
);
};
export default Blog;
Неплохо, но есть проблемы:
- Мы используем
anyдля данных. Это открывает дверь ошибкам. - Нет гарантии, что у каждого поста будут поля
titleиbody.
Теперь добавим типизацию:
// Определяем интерфейс для данных поста
interface Post {
id: number;
title: string;
body: string;
}
// Типизируем результат getStaticProps
import { GetStaticProps } from "next";
export const getStaticProps: GetStaticProps<{ posts: Post[] }> = async () => {
const response = await fetch("https://jsonplaceholder.typicode.com/posts");
const posts: Post[] = await response.json();
return {
props: {
posts,
},
};
};
const Blog = ({ posts }: { posts: Post[] }) => {
return (
<div>
<h1>Блог</h1>
{posts.map((post) => (
<div key={post.id}>
<h2>{post.title}</h2>
<p>{post.body}</p>
</div>
))}
</div>
);
};
export default Blog;
Теперь TypeScript защитит нас от:
- Ошибочного использования полей, которых нет в API-ответе.
- Отсутствия обязательных данных в массиве
posts.
Типизация через интерфейсы — немного глубже
Что делать, если данные сложные? Например, каждый пост может содержать автора с именем и email. Мы можем вложить интерфейсы друг в друга:
interface Author {
name: string;
email: string;
}
interface Post {
id: number;
title: string;
body: string;
author: Author;
}
И теперь, если в API ответа не будет поля author.name, компилятор быстро нас об этом предупредит.
Типизация ответа API
Когда вы работаете с внешним API, важно, чтобы и его структура была типизирована. Иногда API может возвращать дополнительные поля, которые вам не нужны, или JSON может быть оформлен не так, как вы ожидали. Вот пример, как это можно обработать:
// Убедимся, что данные от API соответствуют нашему интерфейсу
const fetchPosts = async (): Promise<Post[]> > {
const response = await fetch("https://jsonplaceholder.typicode.com/posts");
const data = await response.json();
// Для простоты не будем сейчас валидировать поля, но можно использовать библиотеки, такие как Yup или Zod.
return data.map((item: any) => ({
id: item.id,
title: item.title,
body: item.body,
author: {
name: item.userId, // Здесь, например, ошибка, потому что userId не строка!
email: "example@example.com", // Заглушка для демонстрации
},
}));
};
Типизируем компоненты
Теперь, когда данные для страницы четко типизированы, можно улучшить типизацию наших компонентов:
// Компонент для отображения одного поста
const PostCard: React.FC<{ post: Post }> = ({ post }) => {
return (
<div>
<h2>{post.title}</h2>
<p>{post.body}</p>
<small>Автор: {post.author.name}</small>
</div>
);
};
// Используем PostCard внутри основного компонента
const Blog: React.FC<{ posts: Post[] }> = ({ posts }) => {
return (
<div>
<h1>Блог</h1>
{posts.map((post) => (
<PostCard key={post.id} post={post} />
))}
</div>
);
};
Работа с API-ошибками
Иногда API может возвращать ошибочные данные или вовсе не работать. Вот пример, как это обработать:
interface Post {
id: number;
title: string;
body: string;
}
interface ErrorProps {
message: string;
}
export const getStaticProps: GetStaticProps<{
posts?: Post[];
error?: ErrorProps;
}> = async () => {
try {
const response = await fetch("https://jsonplaceholder.typicode.com/posts");
if (!response.ok) {
throw new Error(`Failed to fetch: ${response.statusText}`);
}
const posts: Post[] = await response.json();
return {
props: { posts },
};
} catch (error) {
return {
props: {
error: { message: error.message },
},
};
}
};
const Blog = ({ posts, error }: { posts?: Post[]; error?: ErrorProps }) => {
if (error) {
return <p>Ошибка: {error.message}</p>;
}
return (
<div>
<h1>Блог</h1>
{posts?.map((post) => (
<div key={post.id}>
<h2>{post.title}</h2>
<p>{post.body}</p>
</div>
))}
</div>
);
};
export default Blog;
Стратегии для работы с типизацией API
Если ваш API-ответ сильно варьируется, есть смысл использовать схемы валидации данных, такие как Yup или Zod. Эти библиотеки позволяют описывать данные декларативно и проверять их перед использованием. Например:
import * as yup from "yup";
const PostSchema = yup.object({
id: yup.number().required(),
title: yup.string().required(),
body: yup.string().required(),
});
// Применяем схему при получении данных
const validatePost = (data: any): Post => {
return PostSchema.validateSync(data) as Post;
};
Типизация данных в getStaticProps — это ваш билет к безопасным и надежным приложениям. Она убережет вас от проблем, которые так не хочется искать в продакшене, и гарантирует, что ваш код будет легко читать и поддерживать.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ