Кодим приложение с GraphQL, React Native и AWS AppSync
Russian (Pусский) translation by Ellen Nelson (you can also view the original English article)



В этом уроке, я покажу вам как создать и взаимодействовать с базой даных GraphQL, используя AWS AppSync и React Native. У этого приложения будет функционал для работы в реальном времени и без сети, что-то мы получим «из коробки» от AppSync.
В предыдушей записи мы настроили серверную чать нашего GraphQL, с помощью сервиса Amazon AppSync. Посмотрете её, если вы ещë этого не сделали. Или, если хотите Введение в GraphQL, взгляните на другие материалы.
- GraphQLКодим приложение с GraphQL, React Native и AWS AppSync: Back-End (серверная часть)Надир Дабит
- JavaScriptЧто такое GraphQL?Гиги Сайфан
В этом материале мы завершим всё это, проведя сборку клиента React Native. Проект слишком сложный, чтобы показавать вам шаг за шагом, но я объясню архитектуру проекта и покажу вам ключевые части исходного кода.
Обзор архитектуры приложениия и структуры папок
У нашего приложения будет основная отправная точка, которая будет содержать представление с двумя вкладками. Одна из которых будет списком городов из нашей GraphQL базы, в другой будет поле ввода для нового города. Вкладка Cities будет навигатором, который позволит пользователю выбирать конкретный город.
Главный компонент мы положим в папку source, а остальные папки в каталог src, для хранения мутаций, запросов и подписок GraphQL.
А ещë у нас будет папка assets для хранения изображений.
.jpg)
Создание и настройка клиента React Native
Для справки, взгляните на окончательный код этого приложения в GitHub репозитории, но я опишу некоторые шаги, предпринятые для создания приложения с нуля.
Сначала, мы создали новое приложеие React Native, используя Expo.
Как только мы установили зависимости в новоиспечённом проекте. Для работы GraphQL и AppSync, мы использовали следующие зависимости:
aws-appsync aws-appsync-react graphql-tag react-apollo uuid
Также, мы использовали следующие зависимости для UI:
react-navigation react-native-elements react-native-vector-icons
Так же, была привязана одна установленная библиотека векторных значков Vector Icons:
react-native link react-native vector-icons
После установки наших зависимостей, мы скачали файл AppSync.js из консоли AppSync. В консоли нашего AppSync проекта, внизу мы выбираем React Native и нажали а оранжевую кнопку Download, чтобы скачать этот конфигурационный файл.
В этом конфигурационном файле клиента AppSync содержится информация, необходимая нам для создания нового клиента.
Настройка Provider и Store
Верхний уровень приложения — это то, где мы будем делать наши настройки, чтобы подключить API AppSync с помощью клиента React Native. Если ранее вы использовали Redux или React Apollo, то всё будет вам хорошо знакомо. Если нет, просто запомните, что любой дочерний элемент Provider
, в нашем случае ApolloProvider
, будет иметь доступ к предоставленной ему функциональности.
Следующий код — это наш новый файл App.js, который является основным компонентом, импортированным из нашего указателя index.js.
import React from 'react' import Tabs from './src/Tabs' import AWSAppSyncClient from "aws-appsync"; import { Rehydrated } from 'aws-appsync-react'; import { ApolloProvider } from 'react-apollo'; import appSyncConfig from './aws-exports'; const client = new AWSAppSyncClient({ url: appSyncConfig.graphqlEndpoint, region: appSyncConfig.region, auth: { type: appSyncConfig.authType, apiKey: appSyncConfig.apiKey, } }); const WithProvider = () => ( <ApolloProvider client={client}> <Rehydrated> <Tabs /> </Rehydrated> </ApolloProvider> ); export default WithProvider
В этом файле, мы настраиваем нового клиента AppSync, используя комбинацию из конструктора AWSAppSyncClient
из aws-appsync
и параметров в нашем aws-exports.js файле, который предоставляет GraphQL API URL, регион, тип авторизации и ключ авторизации API.
Затем заворачиваем наш главный указатель – файл Tabs.js, который будет содержать нашу вкладку навигации, в ApolloProvider
и передаём клиенту AppSync, в качестве поддержки клиента. Также обернём компонент Tabs
в компонент Rehydrated
, который импортируем из aws-appsync-react
. Это позволит убедиться, что мы прочли наш кеш из асинхронного хранилища и зарегистировали его, перед сборкой пользовательского интерфейса – UI.
Теперь наше приложение сможет запрашивать данные с указателя из нашего AppSync, а также выполнять мутации и подписки!
Навигация
Главным указателем приложения является навигация, реализованная в файле Tabs.js через React Navigation.
Здесь мы создаём и экспортируем TabNavigator
с двумя вкладками, которые:
- Cities — в этом компоненте перечислены города и сам по себе он является навигатором. Этот компонент является навигатором потому, что мы хотим иметь возможность просматривать каждый отдельный город и просматривать места в городе.
- AddCity — этот компонент является формой, позволяющей добавлять новые города.

Повторные компоненты
В этом приложении только один повторяющийся и повторно используемый компонент, это наш TextInput
. Так как мы будем дублировать его стиль и функционал снова и снова, мы решили сделать отдельным компонентом. Компонент ввода реализован в Input.js.
Список городов и навигация по городу
Главный экран приложения представляет собой список городов, который мы будем получать из GraphQL. Мы хотим переходить от каждого города в списке к детальному просмотру этого города, где мы сможем добавлять места.
Для этого мы создаём собственный StackNavigator — Cities.js и компонент City.js, к которому мы переходим при выборе города. При нажатии на город в списке городов Cities
, мы передаём его название и id в качестве реквизитов к City
.
Cities.js
В этом компоненте, мы получаем данные, используя запрос listCities
, а также мы подписываемся на NewCitySubscription
, таким образом, когда добавляется новый город, даже из другого клиента, мы обрабатываем эту подписку и обновляем наш массив с городами. Запрос listCities
делает наш массив городов доступным в нашем компоненте, в качестве this.props.cities
.
City.js
В этом компоненте, мы передаём город в качестве параметра от навигации (доступен как props.navigation.state.params.city
). Мы используем id
города, чтобы получить список мест для выбранного города, используя запрос listLocations
. Мы подписываемся на новое место подобным образом, которым мы подписываемся на новые города в Cities.js, используя подписку NewLocationSubscription
. Также мы предоставляем функции optimisticResponse
и update
для добавления новых городов. Оптимистичный ответ
Добавление городов
И наконец, нам нужно реализовать функционал добавления новых городов в наш GraphQL API в файле AddCity.js. Для этого нам нужно обернуть мутацию вместе с формой, которая вызывает createCity
, передавая значения из формы.
В AddCity есть функция onAdd
, которую мы определяем в компоненте GraphQL, который не только вписывает новый город в нашу базу данных GraphQL, а ещё реализует оптимистичный UI, используя комбинацию из optimisitcResponse
и update
.
Мутации, запросы и подписки
Мутации, запросы и подписки – основные функции для интеграции с GraphQL API. В нашем приложении, эта функциональность реализована в файлах Cities.js, City.js, и AddCity.js, используя клиент AppSync.
Давайте посмотрим подробнее на то, как мутации, запросы и подписки, реализованы в нашем коде.
Запросы
Для начала, давайте посмотрим на то, как создавать и экспортировать GraphQL зарос, который может взаимодействовать с запросом listCities в нашей AppSync Schema. Этот код содержится в файле src/queries/LIstCities.js.
import gql from 'graphql-tag'; export default gql` query listCities { listCities { items { name country id } } }`
Затем, мы импортируем этот запрос в файл Cities.js, вместе с некоторыми помощниками из react-apollo
, и связываем компонент, который может иметь доступ к этим данным, используя compose
и graphql
из react-apollo
.
import { compose, graphql } from 'react-apollo' import ListCities from './queries/ListCities' class Cities extends React.Component { // class definition here // now have access to this.props.cities } export default compose( graphql(ListCities, { props: props => ({ cities: props.data.listCities ? props.data.listCities.items : [], }) }) )(CityList)
Теперь у нас есть доступ к массиву городов с нашего GraphQL сервера, в качестве параметра. Мы можем использовать this.props.cities
, для разметки нашего массива городов, идущего из GraphQL.
Мутации
Чтобы создать мутацию, для начала нам понадобится создать основную мутацию GraphQL и экспортировать её. Мы делаем это в файле src/mutations/CreateCity.js.
import gql from 'graphql-tag' export default gql` mutation addCity($name: String!, $country: String!, $id: ID!) { createCity(input: { name: $name, country: $country, id: $id }) { name country id } } `
Теперь мы можем импортировать эту мутацию (вместе с помощниками Apollo) в файле AddCity.js и использовать её как компонент:
import { compose, graphql } from 'react-apollo' import AddCityMutation from './mutations/AddCity' class AddCity extends React.Component { // class definition here // now have access to this.props.onAdd() } export default compose( graphql(AddCityMutation, { props: props => ({ onAdd: city => props.mutate({ variables: city }) }) }) )(AddCity)
Теперь у нас есть доступ к параметру onAdd
, в котором мы передаём объект, который мы хотим отправить на мутацию!
Подписки
Подписки позволяют нам подписываться на изменения данных, что позволяет держать их обновлёнными в нашем приложении в реальном времени. Если бы мы изменили нашу базу данных, добавив или удалив город, нам бы хотелось, чтобы наше приложение обновило эти изменения в реальном времени.
Сперва нам надо создать мутацию и экспортировать её, тогда мы получим доступ к ней в нашем клиенте. Мы сохраним это в файле src/subscriptionsNewCitySubscriptions.js.
import gql from 'graphql-tag' export default gql` subscription NewCitySub { onCreateCity { name country id } }`;
Теперь мы можем импортировать и вложить подписку в Cities.js. Мы уже рассмотрели, как получить города через на API. Теперь давайте обновим этот функционал для подписки на новые изменения и обновления массива городов, если добавляется новый город.
import AllCity from './queries/AllCity' import NewCitiesSubscription from './subscriptions/NewCitySubscription'; import { compose, graphql } from 'react-apollo' class Cities extends React.Component { componentWillMount(){ this.props.subscribeToNewCities(); } render() { // rest of component here } } export default compose( graphql(ListCities, { options: { fetchPolicy: 'cache-and-network' }, props: (props) => { return { cities: props.data.listCities ? props.data.listCities.items : [], subscribeToNewCities: params => { props.data.subscribeToMore({ document: NewCitiesSubscription, updateQuery: (prev, { subscriptionData: { data : { onCreateCity } } }) => { return { ...prev, listCities: { __typename: 'CityConnection', items: [onCreateCity, ...prev.listCities.items.filter(city => city.id !== onCreateCity.id)] } } } }) } } } }) )(Cities)
Мы добавляем новый реквизит, названный subscribeToNewCities
, который мы вызываем в componentDidMount
. В подписке, мы передаём в документ (определение подписки) и updateQuery
, чтобы описать то, что мы хотим, чтобы произошло при этом обновлением.
Мы деструктурируем createCity
(содержащий мутацию) из параметра, который передаётся в функцию updateQuery
и возвращаем все существующие значения, вместе с обновлённым массивом listCities
, содержащим предыдущие города и данные нового города, который мы получаем из createCity
.
Оптимистичный UI
Что, если мы не хотим ждать пока подписка вернёт самые свежие данные из нашего API для обновления UI?
Если пользователь создаёт новый город, мы хотим автоматического добавления его в массив городов и отображения его в нашем приложении до получения подтверждения от back-end сервера.
Используя несколько техник и функций, мы можем сделать это с лёгкостью.
Давайте изменим наш AddCityMutation
к этому (вы можете посмотреть этот код в файле AddCity.js):
import { compose, graphql } from 'react-apollo' import AddCityMutation from './mutations/AddCity' class AddCity extends React.Component { // class definition here // now have access to this.props.onAdd() } export default compose( graphql(AddCityMutation, { props: props => ({ onAdd: city => props.mutate({ variables: city, optimisticResponse: { __typename: 'Mutation', createCity: { ...city, __typename: 'City' } }, update: (proxy, { data: { createCity } }) => { const data = proxy.readQuery({ query: ListCities }); data.listCities.items.unshift(createCity); proxy.writeQuery({ query: ListCities, data }); } }) }) }) )(AddCity)
Тут мы добавили два новых свойства, чтобы мутировать объект функции:
-
optimisticResponse
задаёт новый ответ, который вы хотите получить в функции обновления -
update
принимает два аргумента, proxy (который позволяет вам читать кеш) и data (данные), которые вы хотели бы использовать для обновления. Мы читаем текущий кеш (proxy.readQuery
), и это наш новый элемент для массива элементов, потом пишем его обратно в кеш, который обновляет наш UI.
Заключение
GraphQL становится все более популярным. Большая часть сложности, связанной с GraphQL, связана с управлением backend'ом и API. Тем не менее, такие инструменты, как AppSync, абстрагируют эту сложность, освобождая разработчиков от большей части времени настройки и работ на сервере.
Я с нетерпением жду гораздо большего количества инноваций в этом направлении и не могу дождаться, что ещё мы увидим в 2018 году!
Если вы заинтересованы в использовании AppSync вместе с платформой Serverless, ознакомьтесь с этим замечательным введением по их совместному использованию.
Если вы хотите узнать больше о AWS AppSync, я бы порекомендовал заглянуть на домашнюю страницу AppSync и в документацию по построению клиента для GraphQL.
Если вы хотите принять участие в этом проект, вы можете подключиться к нашему GitHub репозиторию. Если у вас есть идеи, можете послать нам PR, или используйт это приложение в качестве отправной точки для вашего следующего проекта на React Native GraphQL!
А пока, проверьте некоторые наши другие уроки по React Native, тут а Envato Tuts+!