Учебник по мутации Аполлона

От статических к многоразовым контейнерам мутаций GraphQL

По своей сути философия GraphQL имеет тенденцию опираться на явную сторону. Просто возьмите его систему типов: благодаря ей вы можете гарантировать, что ваши запросы действительны, что ваши мутации возвращают правильные данные, и даже сделать ваш API самодокументированным.

При этом иногда бывает полезно абстрагироваться от части вашей логики в виде небольших многоразовых утилит. Хороший пример - мутации. Нормальный паттерн имеет один компонент-оболочку Apollo на мутацию.

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

Затем мы сделаем еще один шаг и создадим общую withMutation оболочку, которая позволит нам вызывать любую мутацию на сервере, просто указав ее имя!

Вы узнаете о:

  • Мутации Аполлона.
  • Компоненты высшего порядка.
  • Типы GraphQL.
  • Статические и динамические запросы.

Давайте начнем!

Примечание: эта статья основана на вещах, которые я узнал при создании Vulcan, но она в равной степени применима к любому приложению Apollo, независимо от того, основано оно на Vulcan или нет.

Начиная со статической мутации

Прежде чем мы попытаемся обобщить наш компонент, давайте начнем со стандартной статической мутации. Представим, что мы запрашиваем у сервера результаты getMetaData функции, которая вызывает какой-то сторонний API для предоставления метаданных (например, свойствthumbnailUrl, viewCount и tweetCount) об URL.

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

  • Мы определяем новый MyComponent компонент.
  • Мы определяем новую мутацию getMetaDataMutation GraphQL.
  • Мы передаем getMetaDataMutation утилите graphql, которая вернет новую функцию.
  • Мы вызываем эту функцию в нашем компоненте.
  • Результирующий MyComponentWithMutation компонент теперь имеет доступную mutate опору.

Внутри MyComponent теперь вы можете позвонить:

this.props.mutate({variables: {url: 'http://foo.com'}})

Многоразовые контейнеры для мутаций

Пока все хорошо, но наша настройка не очень гибкая. Если мы хотим добавить ту же мутацию к другому компоненту, нам нужно снова обернуть его graphql(getMetaDataMutation). Как мы можем создать новую withGetMetaDataMutation функцию, которая объединит все это в один аккуратный пакет?

Конечно, мы могли просто сделать:

withGetMetaDataMutation = graphql(getMetaDataMutation)

Но наша цель здесь - максимизировать гибкость и возможность повторного использования, поэтому мы сделаем еще один шаг и вместо этого обернем все это внутри другой функции:

Давайте разберем это:

  • withGetDataMutation принимает компонент в качестве аргумента.
  • gql(`mutation ...`) возвращает нашу мутацию GraphQL.
  • graphql(gql`mutation ...`) возвращает функцию, которая принимает компонент в качестве аргумента.
  • Мы вызываем эту функцию для нашего component аргумента.
  • Возвращаем результат.

Это выполняет то же самое, что и наша первая итерация, но теперь у нас есть структура, необходимая для создания нашей общей оболочки мутации.

Улучшения мутации

Однако это можно улучшить несколькими способами. Во-первых, вместо того, чтобы передавать мутацию в MyComponent как this.props.mutate, мы лучше передадим ее как this.props.getMetaData.

Во-вторых, по умолчанию мутация принимает в качестве аргумента объект со свойством variables (как в {variables: {url: 'http://foo.com'}}). Мы могли бы упростить ситуацию, избавившись от variables и напрямую вызвав функцию on{url: 'http://foo.com'}.

Мы можем использовать props параметр мутации, чтобы исправить обе проблемы.

По сути, это говорит graphql: «мы хотим, чтобы наш компонент получил свойство с именем getMetaData, которое выполняет следующие действия».

Теперь, когда у нас есть мутация, мы можем обернуть ею наш компонент:

import { withGetMetaDataMutation } from 'mutation.js';
export default withGetMetaDataMutation(MyComponent);

А внутри нашего компонента наш обработчик событий будет выглядеть примерно так:

fetchMetaData() {
  this.props.getMetaData({url: url})
    .then(result => {// do something with the result})
    .catch(error => {// do something with the error})
}

Становится динамичным

Пока все хорошо, но если бы мы остановились на этом, нам пришлось бы создавать новый withFooMutation компонент-оболочку для каждой новой мутации, которую мы определяем. Итак, давайте посмотрим, как мы можем это обобщить.

Мы хотим, чтобы наш общий withMutation HoC выполнял следующие действия:

  • Возьмите имя мутации.
  • При необходимости возьмите набор имен и типов аргументов.
  • Передайте функцию мутации как опору компоненту.

Эта функция мутации должна принимать определенные нами аргументы, вызывать мутацию на сервере и возвращать нам возвращаемое значение.

Говоря об этом возвращаемом значении, мы решим, что наши общие мутации всегда будут возвращать объекты JSON черного ящика, чтобы упростить задачу.

Оборачивание компонента

Обратите внимание, что этот новый HoC должен включать параметры. Это означает, что нам нужно изменить способ вызова нашего HoC:

import { withMutation } from 'withMutation.js';
const mutationOptions = {
  name: 'getMetaData',
  args: {url: 'String'}
}
export default withMutation(options)(MyComponent);

Теперь мы используем шаблон hocFunction(options)(component), в котором функция HoC сначала принимает объект options в качестве аргумента, а затем возвращает другую функцию, которая обертывает компонент.

Создание фабрики HoC

Так как же будет выглядеть наша фабрика withMutation HoC?

Хотя запросы GraphQL обычно принимают статические строки в качестве аргументов (вы можете узнать, почему здесь), мы нарушим все правила и сгенерируем наш запрос динамически на основе аргумента options.args, переданного в мутацию.

Напоминаем, что вот код мутации, который мы хотим создать:

mutation getMetaData($url: String) {
  getMetaData(url: $url)
}

Или, если у нас нет аргументов:

mutation getMetaData {
  getMetaData
}

А вот как это выглядит, когда мы динамически генерируем его из нашего options объекта:

Все, что нам нужно сделать сейчас, это вставить эту логику в нашу мутацию:

Как видите, мы заменили каждую жестко запрограммированную строку в нашей исходной мутации на переменные, переданные в качестве опции функции HoC.

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

Между прочим: если вы не знакомы с синтаксисом [name] (я не знал до недавнего времени!), Это одна из многих отличных новых функций ES6.

Типы GraphQL

Пока мы говорили только о клиенте, но сервер тоже играет свою роль. Во-первых, нам нужно определить новый JSON тип для возвращаемого значения мутации с помощью пакета graphql-json-type.

scalar JSON

И преобразователь для этого типа:

import GraphQLJSON from 'graphql-type-json';
// in your resolvers:
JSON: GraphQLJSON

Наконец, давайте не будем забывать о типе самой мутации:

type Mutation {
  getMetaData(url: String) : JSON
}

Определение преобразователя мутаций

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

const rootResolvers = {
  Mutation: {
    getMetaData(root, { url }, context) {
      const data = ... // make some kind of API call
      return data;
    },
  },
};

Теперь мы можем использовать нашу withMutation оболочку на клиенте, и она вызовет getMetaData мутацию на сервере, как и ожидалось!

Напомним, весь код в одном месте:

Вы также можете увидеть готовый продукт в действии в кодовой базе Vulcan.

Спектр абстракции

Как видите, мы перешли от статического, явного подхода, в котором все прописано, к более абстрактному, «магическому» шаблону, который позволяет нам обойти множество шаблонов. Нельзя сказать, что один способ лучше другого: как это часто бывает, у каждого есть свои плюсы и минусы.

Но независимо от того, где вы окажетесь в спектре абстракции, я готов поспорить, что понимание шаблонов, которые мы рассмотрели сегодня, окажется очень полезным, когда дело доходит до овладения мутациями Аполлона!