Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Асинхронные компоненты в React и Next.js
Асинхронные компоненты - это относительно новая фича в React (19+) и Next.js, которая позволяет компонентам использовать async/await напрямую без хуков.
Что такое асинхронный компонент
Асинхронный компонент - это компонент, определённый как async function, который может выполнять асинхронные операции (fetch, работа с БД) перед рендерингом.
// Традиционный подход - useEffect + useState
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
fetch(`/api/users/${userId}`)
.then(res => res.json())
.then(data => {
setUser(data);
setLoading(false);
})
.catch(err => {
setError(err);
setLoading(false);
});
}, [userId]);
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return <div>{user.name}</div>;
}
// Асинхронный компонент - напрямую с async/await
async function UserProfile({ userId }) {
const response = await fetch(`/api/users/${userId}`);
const user = await response.json();
return <div>{user.name}</div>;
}
Основные преимущества асинхронных компонентов
1. Упрощение кода
Нет необходимости в useState, useEffect и обработке loading/error состояний:
// Асинхронный компонент - просто и чисто
async function ArticleList() {
const articles = await fetchArticles();
return (
<ul>
{articles.map(article => (
<li key={article.id}>
<h2>{article.title}</h2>
<p>{article.description}</p>
</li>
))}
</ul>
);
}
2. Загрузка данных на сервере
В Next.js асинхронные компоненты (Server Components) выполняются на сервере, это означает:
- Данные загружаются на сервере, а не в браузере
- Секреты (API ключи) безопасны - они не отправляются в браузер
- Меньше JavaScript отправляется клиенту
// Server Component - выполняется на сервере
async function Database() {
// Сервер может напрямую работать с БД без API
const users = await db.users.findAll();
// API ключи спрятаны на сервере
const apiKey = process.env.SECRET_API_KEY; // Безопасно
return <UserList users={users} />;
}
3. Кэширование данных
Next.js кэширует результаты fetch запросов на сервере:
// Этот запрос будет кэширован по умолчанию
async function CachedData() {
// Одинаковый URL - результат кэшируется
const data1 = await fetch('https://api.example.com/data').then(r => r.json());
const data2 = await fetch('https://api.example.com/data').then(r => r.json());
// data1 и data2 одинаковые, но fetch выполнился один раз
return <div>{data1.title}</div>;
}
4. Параллельная загрузка данных
Легче загружать несколько источников данных параллельно:
// Функция с несколькими зависимостями
async function Dashboard() {
// Загружаем всё параллельно с Promise.all
const [user, stats, posts] = await Promise.all([
fetch('/api/user').then(r => r.json()),
fetch('/api/stats').then(r => r.json()),
fetch('/api/posts').then(r => r.json())
]);
return (
<div>
<UserCard user={user} />
<StatsWidget stats={stats} />
<PostList posts={posts} />
</div>
);
}
Использование в Next.js
Server Components (по умолчанию в Next.js 13+)
// app/page.js - это Server Component по умолчанию
export default async function Home() {
const data = await fetch('https://api.example.com/data').then(r => r.json());
return (
<main>
<h1>{data.title}</h1>
<p>{data.description}</p>
</main>
);
}
Смешивание Server и Client Components
Если тебе нужны интерактивные элементы, используй use client:
// app/page.js - Server Component
export default async function Page() {
const data = await fetchData();
return (
<main>
<h1>{data.title}</h1>
{/* Интерактивный компонент */}
<InteractiveWidget initialData={data} />
</main>
);
}
// components/InteractiveWidget.js - Client Component
'use client';
import { useState } from 'react';
export function InteractiveWidget({ initialData }) {
const [state, setState] = useState(initialData);
return (
<div>
<button onClick={() => setState({ ...state, count: state.count + 1 })}>
Click me
</button>
</div>
);
}
Обработка ошибок и loading состояния
Для асинхронных компонентов используй Suspense и Error boundaries:
import { Suspense } from 'react';
// Компонент, который может "throw" ошибку
async function PostContent({ postId }) {
const post = await fetch(`/api/posts/${postId}`).then(r => {
if (!r.ok) throw new Error('Post not found');
return r.json();
});
return <article>{post.content}</article>;
}
// Используй Suspense для loading состояния
export default function PostPage({ postId }) {
return (
<Suspense fallback={<div>Loading...</div>}>
<PostContent postId={postId} />
</Suspense>
);
}
// Error boundary для обработки ошибок
'use client';
import { useEffect } from 'react';
export default function ErrorBoundary({ error, reset }) {
useEffect(() => {
console.error(error);
}, [error]);
return (
<div>
<h2>Something went wrong!</h2>
<button onClick={reset}>Try again</button>
</div>
);
}
Реальный пример - Blog
// app/blog/[slug]/page.js
import { Suspense } from 'react';
import { notFound } from 'next/navigation';
// Асинхронный Server Component
async function BlogPost({ slug }) {
try {
const post = await fetch(`https://api.example.com/posts/${slug}`, {
next: { revalidate: 60 } // Кэшировать на 60 секунд
}).then(r => {
if (!r.ok) throw new Error('Not found');
return r.json();
});
const comments = await fetch(`https://api.example.com/posts/${slug}/comments`, {
next: { revalidate: 30 } // Комментарии обновляются чаще
}).then(r => r.json());
return (
<article>
<h1>{post.title}</h1>
<p className="text-content-secondary">{post.date}</p>
<div>{post.content}</div>
<section className="mt-8">
<h2>Comments ({comments.length})</h2>
<CommentsList comments={comments} />
</section>
</article>
);
} catch (error) {
notFound();
}
}
// Client Component для интерактивности
'use client';
function CommentsList({ comments }) {
const [liked, setLiked] = useState({});
return (
<div className="space-y-4">
{comments.map(comment => (
<div key={comment.id} className="border-l-4 border-blue-500 pl-4">
<p className="font-semibold">{comment.author}</p>
<p>{comment.text}</p>
<button onClick={() => setLiked(prev => ({
...prev,
[comment.id]: !prev[comment.id]
}))}>
{liked[comment.id] ? 'Unlike' : 'Like'}
</button>
</div>
))}
</div>
);
}
// Layout
export default async function PostPage({ params }) {
return (
<Suspense fallback={<div>Loading post...</div>}>
<BlogPost slug={params.slug} />
</Suspense>
);
}
Ограничения асинхронных компонентов
- Нельзя использовать хуки - это Server Components
- Нельзя использовать контексты - нужен
use()хук - Нельзя использовать event listeners -
onClick,onChangeне работают
// ПЛОХО - попытка использовать хуки в async компоненте
async function BadComponent() {
const [state, setState] = useState(0); // ОШИБКА!
return <div>{state}</div>;
}
// ХОРОШО - async компонент только для загрузки данных
async function GoodComponent() {
const data = await fetchData();
return (
<div>
{/* Интерактивность в Client Component */}
<InteractiveContent data={data} />
</div>
);
}
'use client';
function InteractiveContent({ data }) {
const [state, setState] = useState(0);
return <div onClick={() => setState(state + 1)}>{state}</div>;
}
Когда использовать асинхронные компоненты
- Загрузка данных на сервере - вместо API endpoints
- Безопасное хранение секретов - API ключи на сервере
- Кэширование данных - встроенное в Next.js
- Уменьшение JavaScript - меньше кода отправляется клиенту
- Параллельная загрузка - Promise.all вместо множества useEffect
Заключение
Асинхронные компоненты - это мощный инструмент для загрузки данных и рендеринга контента на сервере. Они упрощают код, улучшают производительность и безопасность, но требуют понимания разницы между Server и Client Components.