Совмещение Typescript и GraphQL Code Generator



Книга Совмещение Typescript и GraphQL Code Generator

GraphQL  —  это открытый язык запросов и управления данными для API.


Нам больше не нужно играть в догадки, как в случае с REST, поскольку этот язык строго типизирован. Перед выполнением запроса инструменты обеспечивают его синтаксическую верность и соответствие системе типов GraphQL.


Так как TypeScript  —  это типизированный язык, он идеально сочетается с GraphQL. Используя их вместе, можно обеспечить согласование типов в бэкенде и фронтенде.


Зачем нам вручную определять интерфейсы TS? При написании кода вручную велика вероятность появления ошибок, поэтому и был разработан graphql-codegen.


Генератор кода GraphQL  —  это инструмент командной строки, способный генерировать типизацию TypeScript на основе схемы GraphQL. При разработке GraphQL-бэкенда возникает много случаев, когда мы пишем то, что уже описано в схеме, только в ином формате. Например, сигнатуры механизмов интерпретации, модели MongoDB, службы Angular и т.д.


graphql-code-generator.com


Если коротко, то это инструмент командной строки, который выполнит за нас всю трудоемкую работу. В этой статье мы познакомимся с ним поглубже и посмотрим, как его можно использовать для создания запроса с помощью Typescript, React и Apollo.


Настройка генератора кода GraphQL


Начнем с установки библиотеки GraphQL:


yarn add graphqlnpm install — save graphql

Далее нужно добавить cli:


yarn add -D @graphql-codegen/clinpm install --save-dev @graphql-codegen/cli

Генератор кода считывает всю конфигурацию из файла codegen.yml. Можно сгенерировать ее автоматически, воспользовавшись мастером установки. Запустим его:


yarn graphql-codegen initnpx graphql-codegen init

Для нашего примера выберем Typescript и Apollo.



После настройки останется только выполнить типичную команду установки:


npm install / yarn install

Чтобы не усложнять, мы будем потреблять демонстрационную конечную точку GraphQL. Можно найти много таких точек здесь.


После настройки codegen.yml будет выглядеть так:


overwrite: true
schema: https://swapi-graphql.netlify.app/.netlify/functions/index
documents: 'src/**/*.gql'
generates:
src/generated/graphql.tsx:
plugins:
- 'typescript'
- 'typescript-operations'
- 'typescript-react-apollo'

Параметр schema сообщает инструменту о местонахождении конечной точки GraphQL, в качестве которой мы будем использовать Star Wars.


В этом сценарии мы применяем схему URL, но для ее определения также можно задействовать локальный файл .qql:


schema: 'src/**/*.graphql'

Или указать файл с несколькими шаблонами:


// Несколько шаблонов
schema:
- 'src/app1/**/*.graphql'
- 'src/app2/**/*.graphql'
// ignores files
schema:
- 'src/**/*.graphql'
- '!src/app2/**/*.graphql'

Параметр documents сообщает инструменту командной строки, откуда извлекать gql fragments/mutations/queries.


Имейте в виду, что генератор кода является голым инструментом, настройка которого производится с помощью плагинов. Обратите внимание на раздел plugins в codegen.yml. Так настраивается ядро.


В этой статье мы сосредоточимся на React с Apollo и Typescript, но его также можно использовать и с любыми другими вендорами или средами. Можно даже выводить код в других языках программирования, например в Java, .Net и Kotlin.


Для автоматической генерации всех типов нужно выполнить:


yarn generate

Обратите внимание, что сейчас мы получим сбой, так как еще не создали файл qql для парсинга.


Error: Unable to find any GraphQL type definitions for the following pointers: 'src/**/*.gql'

Создание запроса GraphQL


В этом примере им будет разбитый на страницы запрос для перечисления всех планет из Звездных войн.


Обратиться к конечной точке Star Wars можно здесь.


Вот как там будет выглядеть наш запрос:


{
allPlanets (first:5, after: "YXJyYXljb25uZWN0aW9uOjQ=") {
planets {
id,
name,
diameter,
population,
gravity
},
pageInfo {
endCursor
}
}
}

Создадим на основе этого запроса файл planets.qql:


query allPlanets($after: String) {
allPlanets(first: 5, after: $after) {
planets {
id
name
diameter
population
gravity
}
pageInfo {
endCursor
}
}
}

Обратите внимание, как мы объявляем запрос с приставкой query. Мы используем $ для объявления переменной $after и указываем для нее тип String. Он не является обязательным и не делает ее однозначно String, так как на начальной странице это значение будет пусто.


Теперь можно сгенерировать первый запрос:


npm generate / yarn generate

Успешная генерация

На этот раз вместо сбоя нас ожидал успех, так как теперь есть запрос для генерации в src/queries/planets.gql. Можете проверить сгенерированный файл src/generated/graphql.tsx и начать использовать его в проекте.


Подстройка генератора


Мы уже сгенерировали запрос с базовой конфигурацией. Теперь посмотрим, как можно улучшить генерируемый код более углубленной настройкой.


В этом инструменте командной строки есть набор предустановленных настроек, с которыми можно ознакомиться здесь. Вот некоторые из них:


config:
withHooks: true
withComponent: false
withHOC: false

Поскольку подход Component и HOC устарел, вам будет рекомендована версия hook. Если вы привыкли следовать рекомендациям, то все будет в порядке. 


Есть несколько достойных внимания вариантов конфигурации. Вот три наиболее, на мой взгляд, актуальных:


1. Функциональное программирование


Сгенерированный код будет придерживаться стиля функционального программирования:


config:
constEnums: true
immutableTypes: true

Это защитит вас от возможной мутации объектов результата запроса и необходимости использовать стиль кода, не допускающий изменения. В TypeScript очень удобно предотвращать мутации с помощью модификатора readonly.


2. Приставка


В некоторых командах принято добавлять приставки, например, к перечислениям и интерфейсам. В инструменте командной строки для этого есть ряд опций:


config:
typesPrefix: I
typesSuffix: I
enumPrefix: false

3. Стиль кода


Некоторые группы разработчиков не любят необязательные поля или используют их особым образом. Этот инструмент позволяет настроить поведение полей optional:


config:
avoidOptionals:
field: true
inputValue: true
object: true
defaultValue: true

В документации по этой части есть и много другой полезной информации.


После настройки конфигурации итоговый файл codegen.yml будет выглядеть так:


overwrite: true
schema: https://swapi-graphql.netlify.app/.netlify/functions/index
documents: 'src/**/*.gql'
generates:
src/generated/graphql.tsx:
plugins:
- 'typescript'
- 'typescript-operations'
- 'typescript-react-apollo'
config:
constEnums: true
immutableTypes: true

Настройка клиента Apollo


Для получения возможности использовать и выполнять сгенерированный запрос, нужно настроить Apollo Client.


yarn add @apollo/client graphqlnpm install @apollo/client graphql

@apollo/client: этот пакет содержит практически все необходимое для настройки Apollo Client. Он включает внутренний кэш памяти, управление локальным состоянием, обработку ошибок и уровень представления на основе React.


graphql: этот пакет предоставляет логику для парсинга запросов GraphQL.


apollographql.com


Обратите внимание, что в codegen.yml мы используем typescript-react-apollo, который уже генерирует запросы Apollo.


Далее настроим Apollo Client и Provider:


import '../styles/globals.css'
import { ApolloProvider, ApolloClient, InMemoryCache } from '@apollo/client';

const client = new ApolloClient({
uri: 'https://swapi-graphql.netlify.app/.netlify/functions/index',
cache: new InMemoryCache()
});

function MyApp({ Component, pageProps }) {
return (
<ApolloProvider client={client}>
<Component {...pageProps} />
</ApolloProvider>
)
}

export default MyApp

Вот теперь можно применять сгенерированный запрос.


Использование сгенерированного GraphQL-запроса


При использовании интеграции Apollo генератор будет предоставлять автоматические или ручные запросы:


// активируется автоматически при рендеринге компонентов
const { data, loading} = useAllPlanetsQuery();
// активируется вручную через вызов метода "getPlanets"
const [getPlanets, { loading, data }] = useAllPlanetsLazyQuery();

В зависимости от ситуации вы будете применять тот или иной вариант  —  тип данных у них будет одинаков. Давайте взглянем на возвращаемый тип для PlanetQuery.


export type AllPlanetsQuery = (
{ readonly __typename?: 'Root' }
& { readonly allPlanets?: Maybe<(
{ readonly __typename?: 'PlanetsConnection' }
& { readonly planets?: Maybe<ReadonlyArray<Maybe<(
{ readonly __typename?: 'Planet' }
& Pick<Planet, 'id' | 'name' | 'diameter' | 'population' | 'gravity'>
)>>>, readonly pageInfo: (
{ readonly __typename?: 'PageInfo' }
& Pick<PageInfo, 'endCursor'>
) }
)> }
)

А теперь посмотрим все это в действии:


import '../styles/globals.css'
import { useAllPlanetsQuery } from './generated/graphql';
import { ApolloProvider, ApolloClient, InMemoryCache } from '@apollo/client';

const client = new ApolloClient({
uri: 'https://swapi-graphql.netlify.app/.netlify/functions/index',
cache: new InMemoryCache()
});

function ListAllPlanets () {
const { data, loading} = useAllPlanetsQuery();

return loading ? (
<div>
loading...
</div>
) : (
<ul>
{data.allPlanets.planets.map((item) => {
return (
<li key={item.id}>
{item.name} - {item.population ? `${item.population} habitants` : 'N/A'}
</li>);
})}
</ul>
)
}

function MyApp() {
return (
<ApolloProvider client={client}>
<ListAllPlanets />
</ApolloProvider>
)
}

export default MyApp

Так как все генерируется автоматически, можно не бояться потери синхронизации между бэкендом и фронтендом. 


Чтобы извлечь из сгенерированного файла обобщенные типы, выполните следующее: 


import type { Planet } from ‘../generated/graphql’;

А этой инструкцией можно получить тип конкретного запроса и извлечь пользовательские типы:


import type { AllPlanetsQuery } from '../generated/graphql';

Заключение


Мы рассмотрели генерацию простого GraphQL-запроса, но этот инструмент способен и на многое другое. Он предлагает такие возможности, как Fragments, Mutations и многие другие плагины. 


Graphql Code Generator выполняет всю тяжелую работу за нас. Его очень легко настроить и адаптировать под ваши потребности. Этот инструмент уменьшает количество ручной работы, в ходе которой нередко возникают ошибки. 


Удачным решением будет выполнять yarn generate в непрерывной интеграции. Это гарантирует стабильность и целостность в процессе разработки. 




651   0  

Comments

    Ничего не найдено.