Apollo graphql client представляет удобный лаконичный спсоб работы с данными в приложениях react. В большинстве случаев все то, что мы привыкли делать с помощью redux, гораздо проще сделать при помощи Apollo graphql client. То, о чем я хотел бы рассказать в этой статье — это что связка react + apollo client + graphql существенно (на порядок) упрощает разработку приложений react.

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

query {
  allUsers {
    id
    name
  }
}

allUsers это имя запроса, а внутри фигурных скобок — имена полей которые будут возвращены. Более сложные запросы могут содержать список параметров и содержать поля вложенных объектов:

query {
  allPosts(orderBy: updatedAt_DESC, limit: 7){
    id
    title
    user {
      id
      name
    }
  }
}

В данном случае параметры orderBy и limit не следует воспринимать как в SQL. Это просто имена параметров которые сами по себе без реализации не делают сортировку или ограничение выборки.

Другие параметры и выходные поля этих запросов можно посмотреть из интерфейса консоли.

В качестве основы для приложения возьмем react-create-app. Дополнительно установим apollo-client и react-router-dom:

npm install apollo-boost react-apollo graphql-tag graphql --save
npm install react-router-dom --save

Изменим основной компонент приложения App.js:

import React from 'react';
import { Route, Switch, BrowserRouter } from 'react-router-dom';
import { ApolloProvider } from 'react-apollo';
import ApolloClient from 'apollo-boost';
import Layout from './Layout';
import AllUsers from './AllUsers';
import TopPosts from './TopPosts';
import NewPost from './NewPost';

const client = new ApolloClient({
  uri: 'https://api.graph.cool/simple/v1/ciyz901en4j590185wkmexyex',
});

const App = () => (
  <ApolloProvider client={client}>
      <BrowserRouter>
        <Layout>
          <Switch>
            <Route exact path='/' component={ AllUsers } />
            <Route exact path='/posts' component={ TopPosts } />
            <Route exact path='/user/:userId' component={ NewPost } />
          </Switch>
        </Layout>
      </BrowserRouter>
  </ApolloProvider>
);

export default App;

AploolProvider и ApolloClient — это все что нужно для того чтобы во всех компонентах можно было использовать graphql.

Проще всего передать данные в компонент с использованием тэга Query. Давайте выведем в компоненте список пользователей (сам запрос мы уже опробовали в консоли раньше):

import React from 'react';
import { Link } from 'react-router-dom'
import { Query } from "react-apollo";
import gql from "graphql-tag";
import TopPosts from './TopPosts';

const AllUsers = () => (
  <Query
    query={gql`
      query {
        allUsers {
          id
          name
        }
      }
    `}
  >
    {({ loading, error, data }) => {
      for (let key in arguments[0])
      console.log(key, arguments[0][key]);
      console.log('data', data)
      if (loading) return <p>Loading...</p>;
      if (error) return <p>Error :(</p>;

      return (
        <ul key='allUsers'>
          {data.allUsers.map(({ id, name }) => (
            <li key={id}><Link  to={`/user/${id}`}>{name ? name : 'incognoito'}</Link></li>
          ))}
        </ul>
      );
    }}
  </Query>
);

export default AllUsers

Для добавления или изменения состояния на сервере используются Mutation:

import React from 'react';
import { Link } from 'react-router-dom'
import { Mutation } from 'react-apollo';
import gql from 'graphql-tag';

const NewPost = (props) => (
  <Mutation
    mutation={gql`
      mutation createPost($text: String!, $title: String!, $userId: ID!){
        createPost(text: $text, title: $title, userId: $userId) {
          id
        }
      }
    `}
  >
    {(createPost, { loading, error, data }) => {
      if (loading) return <p>Loading...</p>;
      if (error) return <p>Error :(</p>;
      let userId, title, text

      return (
        <form  onSubmit={e => {
          e.preventDefault();
          createPost({ variables: {
            userId: userId.value ,
            title: title.value ,
            text: text.value ,
          }});
        }}>
          <input
            type='hidden'
            value={ props.match.params.userId }
            ref={ node =>  userId = node }
          />
          <input
            type='text'
            ref={ node =>  title = node }
          />
          <textarea
            ref={ node =>  text = node }
          />
          <button type='submit' />
        </form>
      );
    }}
  </Mutation>
);

export default NewPost;


Когда я знакомился с примерами компонетов react с использованием apollo graphql client я чувствовал себя не совсем уютно. А все дело в том что разработчик веб-приложения мыслит скорее императивно, чем декларативно. Нам хочется действия. «Установить фильтр», «сохранить запись» и т.п. И когда мы переходим к тому что нам нет необходимости думать о том как данные грузятся с сервера мы вместо того чтобы принять такой подход и использоватьего преимущества задумываемся над тем как мы будем контролировать store.

Дополняю текст еще одним примером, в котором в запросе graphql используется параметр принятый от охватывающего компонента (роута).

import React from 'react';
import { Link } from 'react-router-dom'
import { Query } from 'react-apollo';
import gql from 'graphql-tag';

const Post = (props) => (
  <Query
    query={gql`
      query {
        Post(id: "${props.match.params.postId}") {
          id
          title
          text
        }
      }
   `}
    fetchPolicy='network-only'
  >
    {({ loading, error, data }) => {
      if (loading) return <p>Loading...</p>;
      if (error) return <p>Error :(</p>;
      return (
        <ul key='topPosts'>
          <li>{data.Post.id}</li>
          <li>{data.Post.title}</li>
          <li>{data.Post.text}</li>
        </ul>
      );
    }}
  </Query>
);

export default Post;


Исходный код.

Также Apollo client работает и в условиях серверного рендеринга и универсальных приложений. Предзагрузка данных для серверного рендеринга (включая и все вложенные компоненты) реализована «из коробки». Парадокс в том что соединив несколько технологий которые часто критикуют за усложненность получили в результате существенное упрощение приложения.

apapacy@gmail.com
11 мая 2018 года.

Комментарии (7)


  1. iit
    11.05.2018 06:31

    В данном примере все просто, но вот если в параметрах запроса есть параметры которые завязаны на кучу других компонентов (поиск, сортировка или фильтрация) то все будет не так тривиально. Про связь мутация+форма+валидация я вообще промочу.


    В моем случае пришлось написать подобие orm для всего этого и обернуть её в redux, хотя сейчас я бы лучше использовал для этого mobx.


  1. apapacy Автор
    11.05.2018 07:34

    Спасибо за идею добавлю запрос с параметром в пример


  1. defint
    11.05.2018 08:18
    +1

    Если уж совсем от redux хочется избавится, то можно попробовать apollo-link-state (https://github.com/apollographql/apollo-link-state).
    На данный момент мне не очень понравилась работа с кэшем, но обещают в новой версии добавить хелперы на все случаи жизни.


    1. apapacy Автор
      11.05.2018 09:01

      Я думаю что такие решения как apollo-link-state это развитие темы в немного другую сторону — от чистой декларативности к управлению состоянием. Мои планы в ближайшее время посмотреть в сторону relay. Я его долгое догонял но сейчас кажется есть небольшая надежда


  1. atumisnamor
    11.05.2018 11:43

    «Сразу утчоню» — опечатка :)


  1. psycura
    11.05.2018 18:28
    +1

    Сразу уточню. Apollo graphql client использует redux под капотом — уже нет. с версии 2 Apollo перестали использовать Redux под капотом


    1. apapacy Автор
      11.05.2018 18:29

      Спасибо уточню текст