GraphQL клиенты

Mithril — это лёгкий и высокопроизводительный JavaScript-фреймворк для создания одностраничных приложений. Одной из ключевых задач современных SPA является взаимодействие с сервером через GraphQL. Использование GraphQL вместо REST позволяет гибко запрашивать только необходимые данные, снижая объём передаваемой информации и упрощая управление состоянием приложения.

Подключение GraphQL клиента

В экосистеме JavaScript существуют готовые GraphQL клиенты, такие как Apollo Client, urql или легковесные решения на основе fetch. В контексте Mithril можно использовать любой из них, но для простоты и минимального размера приложения часто применяют встроенные HTTP-запросы через m.request или fetch.

Пример запроса с использованием m.request:

import m from "mithril";

const query = `
  query GetUsers {
    users {
      id
      name
      email
    }
  }
`;

const fetchGraphQL = () => {
  return m.request({
    method: "POST",
    url: "/graphql",
    body: { query },
    headers: { "Content-Type": "application/json" }
  });
};

const UserList = {
  oninit: vnode => {
    vnode.state.users = [];
    fetchGraphQL().then(data => {
      vnode.state.users = data.data.users;
      m.redraw();
    });
  },
  view: vnode => m("ul", vnode.state.users.map(user =>
    m("li", `${user.name} (${user.email})`)
  ))
};

m.mount(document.body, UserList);

Ключевые моменты:

  • m.request автоматически возвращает промис и умеет обрабатывать JSON.
  • В body передаётся объект с полем query.
  • После получения данных используется m.redraw(), чтобы обновить интерфейс.

Работа с переменными в запросах

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

Пример запроса с переменной:

const query = `
  query GetUser($id: ID!) {
    user(id: $id) {
      id
      name
      email
    }
  }
`;

const fetchUser = (id) => {
  return m.request({
    method: "POST",
    url: "/graphql",
    body: { query, variables: { id } },
    headers: { "Content-Type": "application/json" }
  });
};

const UserDetail = {
  oninit: vnode => {
    vnode.state.user = null;
    fetchUser(1).then(data => {
      vnode.state.user = data.data.user;
      m.redraw();
    });
  },
  view: vnode => vnode.state.user
    ? m("div", [
        m("h2", vnode.state.user.name),
        m("p", vnode.state.user.email)
      ])
    : m("p", "Загрузка...")
};

m.mount(document.body, UserDetail);

Особенности:

  • Переменные передаются отдельным объектом variables.
  • Объявление переменных происходит через $имя: тип!.
  • Mithril требует ручного вызова m.redraw(), если данные загружаются асинхронно вне компонентов.

Мутации и обновление данных

GraphQL мутации позволяют создавать, обновлять и удалять данные на сервере. Работа с ними в Mithril аналогична запросам, но с ключевым отличием — после успешной мутации часто требуется локальное обновление состояния компонента.

Пример создания нового пользователя:

const mutation = `
  mutation AddUser($name: String!, $email: String!) {
    addUser(name: $name, email: $email) {
      id
      name
      email
    }
  }
`;

const addUser = (name, email) => {
  return m.request({
    method: "POST",
    url: "/graphql",
    body: { query: mutation, variables: { name, email } },
    headers: { "Content-Type": "application/json" }
  });
};

const AddUserForm = {
  oninit: vnode => {
    vnode.state.name = "";
    vnode.state.email = "";
    vnode.state.users = [];
  },
  view: vnode => m("div", [
    m("input[type=text][placeholder=Имя]", {
      value: vnode.state.name,
      oninput: e => vnode.state.name = e.target.value
    }),
    m("input[type=email][placeholder=Email]", {
      value: vnode.state.email,
      oninput: e => vnode.state.email = e.target.value
    }),
    m("button", {
      onclick: () => addUser(vnode.state.name, vnode.state.email)
        .then(data => {
          vnode.state.users.push(data.data.addUser);
          m.redraw();
        })
    }, "Добавить пользователя"),
    m("ul", vnode.state.users.map(user =>
      m("li", `${user.name} (${user.email})`)
    ))
  ])
};

m.mount(document.body, AddUserForm);

Важные моменты:

  • После мутации данные нужно вставлять в локальное состояние вручную.
  • Использование m.redraw() критично для обновления интерфейса.
  • Mithril не предоставляет автоматическую синхронизацию с сервером, в отличие от Apollo.

Использование подписок GraphQL

Для приложений с реальным временем можно подключать подписки через WebSocket. В Mithril интеграция чаще всего происходит через сторонние библиотеки, такие как subscriptions-transport-ws, с последующей обработкой данных в состоянии компонентов.

Пример базовой подписки:

import { SubscriptionClient } from 'subscriptions-transport-ws';

const client = new SubscriptionClient('ws://localhost:4000/graphql', { reconnect: true });

const NewUserComponent = {
  oninit: vnode => {
    vnode.state.users = [];
    client.request({ query: `subscription { newUser { id name email } }` })
      .subscribe({
        next: data => {
          vnode.state.users.push(data.data.newUser);
          m.redraw();
        }
      });
  },
  view: vnode => m("ul", vnode.state.users.map(user =>
    m("li", `${user.name} (${user.email})`)
  ))
};

m.mount(document.body, NewUserComponent);

Особенности подписок:

  • Данные приходят асинхронно, поэтому важно управлять состоянием компонентов.
  • Mithril требует ручного вызова m.redraw() для обновления интерфейса при поступлении новых данных.
  • Подписки удобно комбинировать с локальным хранилищем для кеширования и обработки данных.

Организация клиентской логики

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

Пример структуры:

/api
  graphql.js        // функции fetchGraphQL, fetchUser, addUser
/components
  UserList.js
  UserDetail.js
  AddUserForm.js

Принципы:

  • Каждая функция GraphQL возвращает промис с данными.
  • Компоненты сосредотачиваются только на отображении и управлении локальным состоянием.
  • Централизация запросов упрощает обновление схемы GraphQL на сервере.

Итоговые рекомендации

  • Использовать m.request для лёгких проектов без сложного кеширования.
  • В больших приложениях подключать специализированные клиенты, такие как Apollo, для управления состоянием и кешем.
  • Разделять логику запросов и компонентов для улучшения поддерживаемости.
  • Всегда контролировать перерисовку с помощью m.redraw() после получения асинхронных данных.