Apollo graphql client — разработка приложений на react.js без redux

  • Tutorial
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, first: 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 года.

Similar posts

AdBlock has stolen the banner, but banners are not teeth — they will be back

More
Ads

Comments 11

    +4

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


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

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

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

              0
              Спасибо уточню текст
              +2
              В запросе получения всех постов («allPosts») нет аргумента «limit» :)
                0
                Исправил limit на first.
                0
                А что делать, если двум и более компонентам надо работать с общими данными?
                  0
                  Если использовать подход декларативный то стора не существует. Есть тоько описание того что мы показываем. Если будутв другом компоненте запрошены данные те же самые то они будут там отбражены согласно описанию этого компонента. Также данные кшируются чтобы избежать лишних запросов к бэкэнду.

                  Only users with full accounts can post comments. Log in, please.