Интеграция TypeScript с Next.js
Чтобы использовать TypeScript в Next.js-проекте, нам нужно совсем немного повозиться — в основном это магия автоматизации:
- Если вы ещё не добавили TypeScript в проект, выполните следующую команду:
npm install --save-dev typescript @types/react @types/node
- После этого создайте пустой файл
tsconfig.jsonв корне проекта:
touch tsconfig.json
- Запустите локальный сервер разработки:
npm run dev
Next.js автоматически сгенерирует tsconfig.json, настроенный под специфику Next.js. Вот пример того, как может выглядеть этот файл:
{
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve"
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
"exclude": ["node_modules"]
}
- После успешной настройки замените файлы с расширением
.jsна.tsx. Теперь вы готовы к работе с TypeScript в полном объёме.
Типизация страниц в Next.js
В Next.js страницы являются React-компонентами, а TypeScript помогает нам определить типы пропсов этих компонентов. Давайте разберём это на практике.
Пример простой страницы с типизацией
Рассмотрим типизацию страницы, которая принимает в пропсах объект пользователя:
// pages/profile.tsx
import React from 'react';
// Определяем интерфейс для пропсов
interface ProfileProps {
user: {
id: number;
name: string;
email: string;
};
}
// Объявляем компонент страницы с типами пропсов
const Profile: React.FC<ProfileProps> = ({ user }) => {
return (
<div>
<h1>Привет, {user.name}!</h1>
<p>ID: {user.id}</p>
<p>Email: {user.email}</p>
</div>
);
};
export default Profile;
Здесь мы определили интерфейс ProfileProps, который описывает структуру объекта user, переданного в компонент. Теперь TypeScript будет следить, чтобы мы не передали неправильные данные.
Типизация данных на уровне getStaticProps и getServerSideProps
В Next.js функции getStaticProps и getServerSideProps используют возвращаемые данные для передачи их в компоненты страниц. Мы можем спокойно типизировать эти данные с помощью TypeScript.
Допустим, у нас есть список продуктов, который мы передаём на страницу:
// pages/products.tsx
import React from 'react';
import { GetStaticProps } from 'next';
// Интерфейс для продукта
interface Product {
id: number;
name: string;
price: number;
}
// Интерфейс для пропсов страницы
interface ProductsPageProps {
products: Product[];
}
// Статические данные для страницы
export const getStaticProps: GetStaticProps<ProductsPageProps> = async () => {
const products = await fetch('https://api.example.com/products').then((res) =>
res.json()
);
return {
props: {
products,
},
};
};
// Компонент страницы
const ProductsPage: React.FC<ProductsPageProps> = ({ products }) => {
return (
<div>
<h1>Список продуктов</h1>
<ul>
{products.map((product) => (
<li key={product.id}>
{product.name} - ${product.price}
</li>
))}
</ul>
</div>
);
};
export default ProductsPage;
Обратите внимание, как мы типизируем результат функции getStaticProps. Теперь, если API вернёт данные, которые не соответствуют ожидаемой структуре, TypeScript сразу сообщит об ошибке.
Пример: Типизация getServerSideProps
Теперь рассмотрим типизацию для страницы, где данные загружаются на сервере перед рендерингом:
// pages/order.tsx
import React from 'react';
import { GetServerSideProps } from 'next';
// Интерфейс для заказа
interface Order {
id: number;
date: string;
total: number;
}
// Интерфейс для пропсов страницы
interface OrderPageProps {
order: Order;
}
// Получение данных о заказе на сервере
export const getServerSideProps: GetServerSideProps<OrderPageProps> = async () => {
const order = await fetch('https://api.example.com/order/123').then((res) =>
res.json()
);
return {
props: {
order,
},
};
};
// Компонент страницы
const OrderPage: React.FC<OrderPageProps> = ({ order }) => {
return (
<div>
<h1>Ваш заказ</h1>
<p>Номер заказа: {order.id}</p>
<p>Дата заказа: {order.date}</p>
<p>Итого: ${order.total}</p>
</div>
);
};
export default OrderPage;
Типизация OrderPageProps и использование её в GetServerSideProps делают наш код удобным для поддержки и обнаружения ошибок.
Типизация маршрутов в Next.js
Next.js поддерживает динамические маршруты через шаблоны в названии файлов, такие как [id].tsx. Мы можем типизировать параметры, используемые в этих маршрутах.
Рассмотрим страницу с динамическим маршрутом /product/[id]:
// pages/product/[id].tsx
import React from 'react';
import { GetStaticProps, GetStaticPaths } from 'next';
import { ParsedUrlQuery } from 'querystring';
// Интерфейс продукта
interface Product {
id: number;
name: string;
price: number;
}
// Интерфейс параметров URL
interface ProductParams extends ParsedUrlQuery {
id: string;
}
// Интерфейс для пропсов страницы
interface ProductPageProps {
product: Product;
}
// Функция получения путей
export const getStaticPaths: GetStaticPaths<ProductParams> = async () => {
const products = await fetch('https://api.example.com/products').then((res) =>
res.json()
);
const paths = products.map((product: Product) => ({
params: { id: product.id.toString() },
}));
return { paths, fallback: false };
};
// Получаем данные о продукте
export const getStaticProps: GetStaticProps<
ProductPageProps,
ProductParams
> = async (context) => {
const id = context.params?.id;
const product = await fetch(
`https://api.example.com/products/${id}`
).then((res) => res.json());
return { props: { product } };
};
// Компонент страницы
const ProductPage: React.FC<ProductPageProps> = ({ product }) => {
return (
<div>
<h1>{product.name}</h1>
<p>Цена: ${product.price}</p>
</div>
);
};
export default ProductPage;
Здесь мы типизировали параметры маршрута ProductParams, данные страницы ProductPageProps и функции getStaticPaths и getStaticProps.
Организация типов в Next.js
Чтобы избежать хаоса, все интерфейсы можно вынести в отдельные файлы в директорию types. Например:
/types
product.ts
order.ts
Теперь ваш код будет ещё более чистым и поддерживаемым.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ