Через REST и Redux

к GraphQL и Apollo

Джумак Евгений

Решение всех проблем

React

Redux

Redux-Saga

REST API для связи

👍 Фронтенд умеет работать с REST

Бекенд умеет работать с REST

Фреймворки умеют работать с REST

Много удобных инструментов

Есть устоявшиеся согласованности

А как можно еще?

React

Redux

Redux-Saga

GraphQL

Главное не поддаваться 🙉

#1 Проблема RESTful API

GET /items/{id}

GET /item/{id}

GET /items

GET /items/all

или

или

POST /items/{id}

PUT /items/{id}

или

200 или 201

400 или 405

500 или 501

🤷‍♂️

#2 Неконсистентность данных

GET /rewards/{id}

GET /user/{id}/rewards/{id}

{
    "id": 17652313,
    "title": "My 100th achievement",
    "xp": 500,
    "date": "2017-05-13 07:59:38"
    ...
}
{
    "id": "17652313",
    "title": null,
    "xp": "500",
    ...
}

Он ждет пока бекендер

добавит новое поле

А как там в GraphQL?

type Reward {
  id: ID!
  title: String!
  xp: Int!
  date: DateTime!
}

Describe your schema

{
  reward(id: "113") {
    title
    date
  }
}

Ask for what you want

{
  "title": "My 100th achievement",
  "date": "2017-05-13 07:59:38"
}

Get predictable results

GraphQL! GraphQL!

Можно использовать fetch

const query = `
  user(id: "200450") {
    email
  }
`;

await fetch("https://example.com/graphql", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ query })
});

Неудобно

Нефункционально

Нет кэширования

Выбираем клиент

Фреймворки

React, React-Native

Framework agnostic

Гибкость

Нужна своя схема

Любая схема

Сложность

Сложно

Терпимо

DX

Отлично

Много ручной работы

SSR

Нет*

Да

Документация

Терпимо

Отлично

Вес

31.2 kb

 

36.2 kb

Простой способ начать

npm install apollo-boost react-apollo graphql

Все необходимое для настройки Apollo Client

Парсер схемы

Интеграция с уровнем представления React

Привет Redux

import ApolloClient, { ApolloProvider } from "apollo-boost";

const client = new ApolloClient({
  uri: "https://48p1r2roz4.sse.codesandbox.io"
});

const App = () => (
  <ApolloProvider client={client}>
    <MyApp />
  </ApolloProvider>
);

🤚 Hello World для Apollo

👍 Higher Order Components

👍 Even Better

Query component props

🧙‍♂️ fetchPolicy

📖 Relay и Offset-based пагинация

{
  allFilms(
    first: 5
    after: "YXJyYXljb25uZWN0aW9uOjQ="
  ) {
    edges {
      node {
        title
      }
    }
    pageInfo {
      hasPreviousPage
      hasNextPage
      startCursor
      endCursor
    }
  }
}
{
  allFilms(first: 5, skip: 5) {
    title
  }
}

📖 Пагинация с fetchMore()

<button
  onClick={() =>
    fetchMore({
      variables: {
        after: data.allFilms.pageInfo.endCursor
      },
      updateQuery: (prev, { fetchMoreResult }) => ({
        allFilms: {
          ...fetchMoreResult.allFilms,
          edges: [
            ...prev.allFilms.edges,
            ...fetchMoreResult.allFilms.edges
          ]
        }
      })
    })
  }
>
  Load more
</button>

Cache 🤦‍♂️

📦 Apollo кэширует сущности

type User {
    id
    name
}

🛠 dataIdFromObject()

import { InMemoryCache, defaultDataIdFromObject } from "apollo-cache-inmemory";

const cache = new InMemoryCache({
  dataIdFromObject: object => {
    switch (object.__typename) {
      case "foo":
        // use `key` as the primary key
        return object.key;
      case "bar":
        // use `bar` prefix and `blah` as the primary key
        return `bar:${object.blah}`;
      default:
        // fall back to default handling
        return defaultDataIdFromObject(object);
    }
  }
});

🧙‍♂️ Optimistic UI updates

🧙‍♂️ Automatic cache updates

{
  post(id: '5') {
    id
    score
  }
}
mutation {
  upvotePost(id: '5') {
    id
    score
  }
}
"Post:5"

{
    id: 1
    score: 6.4
}
"Post:5"

{
    id: 1
    score: 7.8
}

📝 Чтение / запись в кэш

const { todo } = client.readQuery({
  query: gql`
    query ReadTodo {
      todo(id: 5) {
        text
      }
    }
  `,
});
const todo = client.readFragment({
  // returned by `dataIdFromObject`
  id: ...,
  fragment: gql`
    fragment myTodo on Todo {
      text
    }
  `,
});

readQuery()

readFragment()

🤷‍♂️ refetchQueries() в мутации

mutate({
  //... insert comment mutation
  refetchQueries: ['comments'],
})

📬 Subscriptions

const DontReadTheComments = ({ repoFullName }) => (
  <Subscription
    subscription={COMMENTS_SUBSCRIPTION}
    variables={{ repoFullName }}
  >
    {({ data: { commentAdded }, loading }) => (
      <h4>New comment: {!loading && commentAdded.content}</h4>
    )}
  </Subscription>
);

👍 SSR with ssr prop

<Query query={GET_USER_WITH_ID} ssr={false}>
  {({ data }) => <span>I won't be run on the server</span>}
</Query>

Сетевые состояния

👩‍💻 Больше состояний

🧙‍♂️ Еще больше состояний

👨‍💻 Батчинг запросов и мутаций

Batched операции по скорости всегда равны самому медленному запросу

🧙‍♂️ Типизация

npm install -g apollo-codegen

apollo-codegen introspect-schema http://localhost:8080/graphql \
    --output schema.json

# TypeScript
apollo-codegen generate **/*.graphql \
    --schema schema.json --target typescript \
    --output operation-result-types.ts

# Flow
apollo-codegen generate **/*.graphql \
    --schema schema.json --target flow \
    --output operation-result-types.flow.js

# Scala
apollo-codegen generate **/*.graphql \
    --schema schema.json --target scala \
    --output operation-result-types.scala

🐞 Отлично! Как там с багами?

💆‍♀️ Вам нужен GraphQL если

👉 Вам приходится ждать изменений от бекенда

У вас сложные, многоуровневые запросы

Вы испытываете проблемы с REST

Вам нужны удобные инструменты работы со схемой

Вы хотите интроспекции в IDE

Тесты

DevTools

Интеграция в IDE

Recompose

Babel

Валидация

Обработка ошибок

Playground

Server Side

Made with Slides.com