GraphQL

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

GraphQL — это язык запросов для API, позволяющий клиенту точно указывать, какие данные нужны, и получать их в одной структуре. В сочетании со SkateJS он открывает возможности для построения компонентов с реактивными интерфейсами и оптимизированным обменом данными.


Создание SkateJS-компонента с GraphQL-запросом

В SkateJS каждый компонент определяется с помощью функции skate, куда передается объект конфигурации с жизненным циклом, свойствами и рендерингом. Интеграция с GraphQL начинается с определения свойства для хранения данных и метода для выполнения запроса.

import { skate, h } from 'skatejs';
import { request, gql } from 'graphql-request';

const endpoint = 'https://example.com/graphql';

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

skate('user-card', {
  props: {
    userId: { attribute: true },
    userData: { default: null }
  },
  async connected(element) {
    if (this.userId) {
      this.userData = await request(endpoint, USER_QUERY, { id: this.userId });
      element.update();
    }
  },
  render() {
    if (!this.userData) {
      return h('div', 'Загрузка...');
    }
    const { name, email } = this.userData.user;
    return h('div', [
      h('h2', name),
      h('p', email)
    ]);
  }
});

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

  • props задают реактивные свойства компонента, включая userId для идентификатора пользователя и userData для хранения результата GraphQL.
  • Метод connected вызывается при присоединении элемента к DOM, что удобно для запуска асинхронного запроса.
  • Использование request из graphql-request упрощает отправку запросов и получение данных.
  • После получения данных вызывается element.update() для повторного рендера компонента.

Реактивные свойства и автоматическое обновление

SkateJS автоматически следит за изменением свойств компонента. Это позволяет обновлять данные при изменении входного параметра без необходимости вручную управлять состоянием.

skate('user-card', {
  props: {
    userId: { attribute: true },
    userData: { default: null }
  },
  async updated(prevProps) {
    if (prevProps.userId !== this.userId) {
      this.userData = await request(endpoint, USER_QUERY, { id: this.userId });
    }
  },
  render() {
    return this.userData
      ? h('div', [
          h('h2', this.userData.user.name),
          h('p', this.userData.user.email)
        ])
      : h('div', 'Загрузка...');
  }
});

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

  • Метод updated(prevProps) позволяет реагировать на изменение свойств и выполнять асинхронные действия, например повторные запросы к GraphQL.
  • Это делает компонент динамическим и готовым к любым изменениям внешнего состояния.

Обработка ошибок и состояния загрузки

Для построения устойчивого компонента важно обрабатывать возможные ошибки сети или некорректные данные:

skate('user-card', {
  props: {
    userId: { attribute: true },
    userData: { default: null },
    error: { default: null }
  },
  async updated(prevProps) {
    if (prevProps.userId !== this.userId) {
      this.error = null;
      this.userData = null;
      try {
        this.userData = await request(endpoint, USER_QUERY, { id: this.userId });
      } catch (err) {
        this.error = err.message;
      }
    }
  },
  render() {
    if (this.error) return h('div', `Ошибка: ${this.error}`);
    if (!this.userData) return h('div', 'Загрузка...');
    return h('div', [
      h('h2', this.userData.user.name),
      h('p', this.userData.user.email)
    ]);
  }
});

Преимущества подхода:

  • Свойство error хранит сообщение об ошибке и позволяет отобразить его пользователю.
  • Разделение состояний загрузки, ошибки и готовности данных делает компонент предсказуемым и поддерживаемым.

Использование нескольких GraphQL-запросов

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

const POSTS_QUERY = gql`
  query getPosts($userId: ID!) {
    posts(userId: $userId) {
      id
      title
    }
  }
`;

skate('user-dashboard', {
  props: {
    userId: { attribute: true },
    userData: { default: null },
    postsData: { default: [] }
  },
  async updated(prevProps) {
    if (prevProps.userId !== this.userId) {
      const [user, posts] = await Promise.all([
        request(endpoint, USER_QUERY, { id: this.userId }),
        request(endpoint, POSTS_QUERY, { userId: this.userId })
      ]);
      this.userData = user;
      this.postsData = posts.posts;
    }
  },
  render() {
    if (!this.userData) return h('div', 'Загрузка...');
    return h('div', [
      h('h2', this.userData.user.name),
      h('ul', this.postsData.map(post => h('li', post.title)))
    ]);
  }
});

Важные аспекты:

  • Promise.all позволяет запускать несколько запросов параллельно, сокращая время ожидания.
  • Хранение данных в отдельных свойствах делает их независимыми и управляемыми через реактивную модель SkateJS.

Встроенные хуки жизненного цикла для GraphQL

SkateJS предоставляет гибкие хуки для управления состоянием данных:

  • connected — первый вызов при добавлении в DOM, подходит для первичных запросов.
  • updated(prevProps) — реагирует на изменения свойств и выполняет повторные запросы.
  • disconnected — освобождает ресурсы при удалении элемента (например, отмена подписок на GraphQL Subscriptions).
  • render — отвечает за отображение данных, учитывая состояние загрузки и ошибок.

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


Интеграция с GraphQL Subscriptions

Для получения обновлений в реальном времени используется GraphQL Subscriptions через WebSocket. SkateJS позволяет подписываться на события и обновлять состояние:

import { createClient } from 'graphql-ws';

const client = createClient({ url: 'wss://example.com/graphql' });
const USER_UPDATED_SUBSCRIPTION = gql`
  subscription onUserUpdated($id: ID!) {
    userUpdated(id: $id) {
      id
      name
      email
    }
  }
`;

skate('live-user-card', {
  props: {
    userId: { attribute: true },
    userData: { default: null }
  },
  connected() {
    client.subscribe(
      { query: USER_UPDATED_SUBSCRIPTION, variables: { id: this.userId } },
      {
        next: ({ data }) => {
          this.userData = data.userUpdated;
          this.update();
        }
      }
    );
  },
  render() {
    if (!this.userData) return h('div', 'Загрузка...');
    return h('div', [
      h('h2', this.userData.name),
      h('p', this.userData.email)
    ]);
  }
});
  • Использование подписок позволяет автоматически обновлять интерфейс при изменении данных на сервере.
  • SkateJS реагирует на изменения свойства userData, вызывая повторный рендер компонента.

Резюме возможностей интеграции SkateJS с GraphQL

  • Декларативное определение компонентов через skate.
  • Реактивные свойства позволяют автоматически обновлять интерфейс при изменении данных.
  • Асинхронные методы жизненного цикла (connected, updated) для выполнения GraphQL-запросов.
  • Поддержка нескольких запросов и подписок для сложных интерфейсов.
  • Обработка ошибок и состояния загрузки встроена в реактивную модель компонентов.
  • Гибкость и модульность: каждый компонент управляет своими данными независимо, упрощая поддержку и расширение приложения.

Такой подход позволяет строить мощные веб-компоненты с оптимизированной работой с API, высокой реактивностью и полной интеграцией с современными возможностями GraphQL.