12. Fragments

Fragments — механизм переиспользования частей запросов. Как компоненты в React, только для GraphQL.
Базовый синтаксис
Заголовок раздела «Базовый синтаксис»fragment UserBasic on User { id name avatar}
fragment UserFull on User { ...UserBasic email bio createdAt posts { id title }}
query GetUser($id: ID!) { user(id: $id) { ...UserFull }}Зачем нужны fragments
Заголовок раздела «Зачем нужны fragments»Без фрагментов — дублирование:
# ❌ Повторяем поля пользователя вездеquery GetPost($id: ID!) { post(id: $id) { title author { id # дублируем name # дублируем avatar # дублируем } comments { author { id # дублируем снова name # дублируем снова avatar # дублируем снова } } }}С фрагментами:
# ✅ Определяем один раз, используем вездеfragment AuthorInfo on User { id name avatar}
query GetPost($id: ID!) { post(id: $id) { title author { ...AuthorInfo } comments { body author { ...AuthorInfo } } }}Fragments в Apollo Client (React)
Заголовок раздела «Fragments в Apollo Client (React)»import { gql, useQuery } from '@apollo/client';
// Определяем фрагментыconst USER_BASIC_FRAGMENT = gql` fragment UserBasic on User { id name avatar email }`;
const POST_CARD_FRAGMENT = gql` fragment PostCard on Post { id title excerpt createdAt author { ...UserBasic } } ${USER_BASIC_FRAGMENT}`;
// Используем в запросеconst GET_POSTS = gql` query GetPosts { posts { items { ...PostCard } total } } ${POST_CARD_FRAGMENT}`;
function PostList() { const { data, loading } = useQuery(GET_POSTS);
if (loading) return <p>Загрузка...</p>;
return ( <div> {data.posts.items.map(post => ( <PostCard key={post.id} post={post} /> ))} </div> );}Co-located Fragments (лучшая практика)
Заголовок раздела «Co-located Fragments (лучшая практика)»Держи фрагмент рядом с компонентом, который его использует:
import { gql } from '@apollo/client';
// Фрагмент объявлен рядом с компонентомexport const POST_CARD_FRAGMENT = gql` fragment PostCardFragment on Post { id title excerpt cover createdAt author { id name avatar } }`;
export function PostCard({ post }) { return ( <div className="post-card"> <img src={post.cover} alt={post.title} /> <h2>{post.title}</h2> <p>{post.excerpt}</p> <div className="author"> <img src={post.author.avatar} alt={post.author.name} /> <span>{post.author.name}</span> </div> <time>{new Date(post.createdAt).toLocaleDateString()}</time> </div> );}import { POST_CARD_FRAGMENT, PostCard } from '../components/PostCard';
const GET_BLOG_POSTS = gql` query GetBlogPosts { posts(status: PUBLISHED, limit: 10) { items { ...PostCardFragment # Используем фрагмент компонента } total } } ${POST_CARD_FRAGMENT}`;
function BlogPage() { const { data } = useQuery(GET_BLOG_POSTS); return ( <div> {data?.posts.items.map(post => ( <PostCard key={post.id} post={post} /> ))} </div> );}Преимущества: Если нужно добавить поле в PostCard — меняем только фрагмент в одном месте, все запросы обновляются автоматически.
Inline Fragments
Заголовок раздела «Inline Fragments»Inline fragments используются для polymorphic типов (interface/union):
query GetSearchResults($query: String!) { search(query: $query) { ... on User { # Если результат — User id name email } ... on Post { # Если результат — Post id title excerpt } ... on Product { # Если результат — Product id name price } }}В Apollo Client с TypeScript:
const SEARCH_QUERY = gql` query Search($query: String!) { search(query: $query) { __typename # ОБЯЗАТЕЛЬНО для union типов! ... on User { id name } ... on Post { id title } } }`;
function SearchResults({ data }) { return data.search.map((item, i) => { switch (item.__typename) { case 'User': return <UserCard key={i} user={item} />; case 'Post': return <PostCard key={i} post={item} />; default: return null; } });}Fragments на сервере: useFragment (Apollo Client 3.7+)
Заголовок раздела «Fragments на сервере: useFragment (Apollo Client 3.7+)»import { useFragment, gql } from '@apollo/client';
const USER_FRAGMENT = gql` fragment UserData on User { id name email avatar }`;
function UserCard({ userId }) { // Читаем фрагмент прямо из кэша const { data, complete } = useFragment({ fragment: USER_FRAGMENT, fragmentName: 'UserData', from: { __typename: 'User', id: userId }, });
if (!complete) return <Skeleton />;
return ( <div> <img src={data.avatar} alt={data.name} /> <h3>{data.name}</h3> </div> );}Практика
Заголовок раздела «Практика»- Создай
UserFragmentс основными полями и используй его в 3 разных запросах - Реализуй co-located fragment для компонента
CommentItem - Напиши запрос с inline fragments для union типа
SearchResult = Post | User | Product - Создай
PageInfoFragmentдля переиспользования полей пагинации - Попробуй
useFragmentдля чтения данных из кэша без запроса
- Fragments — переиспользуемые части запросов
- Co-located фрагменты — рядом с компонентом, который их использует
- Inline fragments — для union и interface типов
__typename— обязательно для определения типа в unionuseFragment— чтение из кэша без нового запроса
Следующий урок → Variables и Arguments →