Hyperapp использует чистые функции для описания состояния и обновлений, что накладывает строгие ограничения на работу с побочными эффектами. Любая логика, изменяющая состояние вне непосредственного обновления состояния (например, асинхронные операции, работа с API, таймеры, локальное хранилище), должна оформляться через эффекты (effects). Это позволяет сохранять детерминированность и предсказуемость приложения.
Эффект в Hyperapp — это объект с ключами effect и
dispatch. Простейший синтаксис:
const fetchUserEffect = (dispatch, props) => {
fetch(`https://api.example.com/users/${props.id}`)
.then(res => res.json())
.then(data => dispatch({ type: "SET_USER", user: data }))
}
Использование эффекта в действии:
const actions = {
loadUser: id => [
{ type: "NO_OP" }, // изменение состояния при необходимости
fetchUserEffect, // эффект будет выполнен
id // параметры передаются в эффект
],
setUser: user => state => ({ ...state, user })
}
Ключевые моменты:
dispatch, чтобы
инициировать изменение состояния через обычное действие.В Hyperapp 2 эффекты могут быть представлены как массив из двух
элементов [effectFn, props]. Первый элемент — функция
эффекта, второй — объект или значение, которое будет передано в функцию.
Пример:
const logEffect = (dispatch, message) => {
console.log(message)
}
const actions = {
logMessage: message => [logEffect, message]
}
При вызове actions.logMessage("Hello World") эффект
выполнится и выведет сообщение в консоль.
Асинхронные операции полностью реализуются внутри эффекта, используя промисы или async/await. Например, загрузка данных с сервера:
const fetchTodos = async (dispatch) => {
try {
const response = await fetch("/api/todos")
const todos = await response.json()
dispatch({ type: "SET_TODOS", todos })
} catch (error) {
dispatch({ type: "SET_ERROR", error })
}
}
const actions = {
loadTodos: () => [fetchTodos]
}
Особенности асинхронных эффектов:
Hyperapp позволяет комбинировать несколько эффектов в одном действии. Для этого действия могут возвращать массив эффектов и действий:
const actions = {
init: () => [
{ type: "INIT_STATE" },
fetchTodos,
logEffect, "Инициализация завершена"
]
}
Порядок выполнения:
dispatch.dispatch внутри эффектов.Для эффектов, создающих подписки или таймеры, важно возвращать
функцию очистки (cleanup) для предотвращения утечек
памяти:
const timerEffect = (dispatch) => {
const id = setInterval(() => dispatch({ type: "TICK" }), 1000)
return () => clearInterval(id) // cleanup
}
const actions = {
startTimer: () => [timerEffect]
}
Hyperapp автоматически вызывает возвращённую функцию при обновлении состояния, если эффект был перезаписан другим действием.
const fetchPosts = async (dispatch) => {
const res = await fetch("/api/posts")
const posts = await res.json()
dispatch({ type: "SET_POSTS", posts })
}
const actions = {
loadPosts: () => [fetchPosts]
}
const resizeEffect = (dispatch) => {
const handler = () => dispatch({ type: "WINDOW_RESIZE", size: window.innerWidth })
window.addEventListener("resize", handler)
return () => window.removeEventListener("resize", handler)
}
const actions = {
subscribeResize: () => [resizeEffect]
}
const saveToLocalStorage = (dispatch, state) => {
localStorage.setItem("appState", JSON.stringify(state))
}
const actions = {
saveState: () => [saveToLocalStorage]
}
Эффекты в Hyperapp создают чёткое разделение между чистым
управлением состоянием и побочными действиями, позволяя строить
масштабируемые и легко тестируемые приложения. Каждое взаимодействие с
внешним миром оформляется отдельной функцией-эффектом, которая всегда
получает доступ к dispatch и может безопасно инициировать
последующие изменения состояния.