HTTP-запросы и работа с API

Hyperapp — это минималистичный фронтенд-фреймворк, который сочетает виртуальный DOM, управление состоянием и реактивные обновления. Несмотря на компактность, Hyperapp предоставляет гибкость для интеграции с внешними API и выполнения HTTP-запросов. Работа с сетью в Hyperapp обычно строится вокруг действий (actions), которые изменяют состояние приложения на основе полученных данных.


Использование fetch в действиях

Для взаимодействия с RESTful API используется стандартная функция JavaScript fetch. Основная идея — отправка запроса к серверу, обработка ответа и обновление состояния через действия.

Пример базового запроса:

import { h, app } from "https://unpkg.com/hyperapp?module";

const state = {
  todos: [],
  loading: false,
  error: null
};

const actions = {
  fetchTodos: () => async (state, actions) => {
    actions.setLoading(true);
    try {
      const response = await fetch("https://jsonplaceholder.typicode.com/todos");
      if (!response.ok) throw new Error("Ошибка сети");
      const data = await response.json();
      actions.setTodos(data);
    } catch (err) {
      actions.setError(err.message);
    } finally {
      actions.setLoading(false);
    }
  },
  setTodos: (todos) => ({ todos }),
  setLoading: (loading) => ({ loading }),
  setError: (error) => ({ error })
};

const view = (state, actions) => (
  h("div", {}, [
    state.loading && h("p", {}, "Загрузка..."),
    state.error && h("p", {}, `Ошибка: ${state.error}`),
    h("ul", {}, state.todos.map(todo => h("li", {}, todo.title))),
    h("button", { onclick: actions.fetchTodos }, "Загрузить список")
  ])
);

app({ init: state, view, actions, node: document.getElementById("app") });

В этом примере:

  • Асинхронные действия позволяют выполнять HTTP-запросы без блокировки интерфейса.
  • Обработка ошибок реализована через блок try/catch.
  • Состояние приложения (loading, error, todos) управляется действиями, что обеспечивает реактивное обновление интерфейса.

Параметры запроса и POST-запросы

Для отправки данных на сервер используется объект конфигурации fetch, включающий метод, заголовки и тело запроса:

const actions = {
  addTodo: (title) => async (state, actions) => {
    try {
      const response = await fetch("https://jsonplaceholder.typicode.com/todos", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ title, completed: false })
      });
      if (!response.ok) throw new Error("Не удалось создать задачу");
      const newTodo = await response.json();
      actions.setTodos([...state.todos, newTodo]);
    } catch (err) {
      actions.setError(err.message);
    }
  }
};

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

  • method: "POST" определяет тип запроса.
  • Заголовок Content-Type важен для JSON.
  • Тело запроса сериализуется через JSON.stringify.
  • После успешного ответа состояние обновляется новым элементом.

Управление состоянием при асинхронных запросах

В Hyperapp состояние должно оставаться предсказуемым. При работе с сетью рекомендуется:

  1. Создавать отдельные поля для статуса загрузки (loading) и ошибок (error).
  2. Использовать отдельные действия для обновления этих полей.
  3. Не изменять напрямую глобальные данные внутри асинхронной функции — всегда через действия.

Пример структуры состояния:

const state = {
  data: null,
  loading: false,
  error: null
};

Асинхронное действие:

const actions = {
  fetchData: () => async (state, actions) => {
    actions.setLoading(true);
    actions.setError(null);
    try {
      const response = await fetch("/api/data");
      const data = await response.json();
      actions.setData(data);
    } catch (err) {
      actions.setError(err.message);
    } finally {
      actions.setLoading(false);
    }
  },
  setData: (data) => ({ data }),
  setLoading: (loading) => ({ loading }),
  setError: (error) => ({ error })
};

Такой подход делает интерфейс отзывчивым и устойчивым к сетевым ошибкам.


Работа с динамическими URL и параметрами

Часто требуется отправлять запросы с динамическими параметрами:

const actions = {
  fetchUser: (userId) => async (state, actions) => {
    try {
      const response = await fetch(`https://jsonplaceholder.typicode.com/users/${userId}`);
      const user = await response.json();
      actions.setUser(user);
    } catch (err) {
      actions.setError(err.message);
    }
  },
  setUser: (user) => ({ user }),
  setError: (error) => ({ error })
};
  • Шаблонные строки позволяют легко формировать URL с параметрами.
  • Обновление состояния через отдельное действие обеспечивает реактивность интерфейса.

Интеграция с RESTful API и сторонними сервисами

Hyperapp подходит для интеграции с любыми REST или GraphQL API. Для REST достаточно использовать fetch с методами GET, POST, PUT, DELETE. Для GraphQL обычно отправляется POST-запрос с JSON-телом:

const actions = {
  fetchGraphQL: (query) => async (state, actions) => {
    try {
      const response = await fetch("/graphql", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ query })
      });
      const result = await response.json();
      actions.setData(result.data);
    } catch (err) {
      actions.setError(err.message);
    }
  }
};
  • Используется единый эндпоинт /graphql.
  • Тело запроса содержит строку GraphQL-запроса.
  • Ответ обрабатывается аналогично REST.

Обработка нескольких запросов одновременно

Для параллельных запросов можно использовать Promise.all:

const actions = {
  fetchAllData: () => async (state, actions) => {
    actions.setLoading(true);
    try {
      const [usersRes, postsRes] = await Promise.all([
        fetch("/users"),
        fetch("/posts")
      ]);
      const users = await usersRes.json();
      const posts = await postsRes.json();
      actions.setUsers(users);
      actions.setPosts(posts);
    } catch (err) {
      actions.setError(err.message);
    } finally {
      actions.setLoading(false);
    }
  }
};

Такой подход уменьшает время ожидания при множественных зависимых или независимых запросах.


Рекомендации по организации кода

  • Разделять actions для API и actions для синхронного обновления состояния.
  • Создавать отдельные модули для работы с API, чтобы основной код интерфейса оставался чистым.
  • Обрабатывать статусы загрузки и ошибки на уровне состояния, чтобы избежать «замороженного» интерфейса.

Hyperapp позволяет писать компактный, но мощный код для работы с HTTP-запросами, сохраняя реактивность и управляемость состояния.