Rollback стратегии

Rollback стратегии в Preact представляют собой набор методов управления состоянием приложения при возникновении ошибок или некорректных изменений данных. Они позволяют откатить изменения, возвращая интерфейс и данные в безопасное состояние без перезагрузки страницы. В Preact, как и в других реактивных библиотеках, управление состоянием тесно связано с виртуальным DOM, что делает применение rollback стратегий более гибким и эффективным.


Реактивное состояние и важность отката

Состояние компонентов в Preact обычно хранится через хуки useState и useReducer. Изменение состояния инициирует обновление виртуального DOM, после чего происходит диффинг и отрисовка только изменившихся элементов. Ошибки в обработчиках событий или асинхронных запросах могут привести к неконсистентности UI.

Rollback стратегия решает эту проблему следующим образом:

  1. Сохранение предыдущего состояния перед изменением.
  2. Применение изменений к текущему состоянию.
  3. Обнаружение ошибки или нежелательного исхода.
  4. Откат состояния к сохраненной версии.

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


Сохранение и откат состояния

Простейший пример реализации rollback стратегии с использованием useState:

import { h } from 'preact';
import { useState } from 'preact/hooks';

function Counter() {
    const [count, setCount] = useState(0);
    const [prevCount, setPrevCount] = useState(0);

    const increment = () => {
        setPrevCount(count);
        try {
            if (Math.random() < 0.3) throw new Error("Simulated error");
            setCount(count + 1);
        } catch (error) {
            setCount(prevCount);
            console.error("Rollback to previous state:", prevCount);
        }
    };

    return (
        <div>
            <p>Count: {count}</p>
            <button onCl ick={increment}>Increment</button>
        </div>
    );
}

В этом примере при каждом изменении сохраняется предыдущее состояние, которое используется для отката при возникновении ошибки.


Rollback при асинхронных операциях

Асинхронные операции, такие как запросы к API, требуют отдельной обработки, поскольку ошибка может возникнуть после того, как состояние уже изменено. В Preact это решается через комбинацию useState и async/await:

import { h } from 'preact';
import { useState } from 'preact/hooks';

function DataFetcher() {
    const [data, setData] = useState(null);
    const [prevData, setPrevData] = useState(null);

    const fetchData = async () => {
        setPrevData(data);
        try {
            const response = await fetch('/api/data');
            if (!response.ok) throw new Error("Failed to fetch");
            const result = await response.json();
            setData(result);
        } catch (error) {
            setData(prevData);
            console.error("Rollback after fetch error", error);
        }
    };

    return (
        <div>
            <button onCl ick={fetchData}>Load Data</button>
            <pre>{JSON.stringify(data, null, 2)}</pre>
        </div>
    );
}

Здесь prevData выполняет роль контрольной точки, к которой происходит откат в случае ошибки.


Использование useReducer для сложных rollback стратегий

Для более сложного состояния, особенно когда изменения касаются нескольких полей или вложенных объектов, удобнее применять useReducer.

import { h } from 'preact';
import { useReducer } from 'preact/hooks';

const initialState = { value: 0, prevValue: 0 };

function reducer(state, action) {
    switch (action.type) {
        case 'INCREMENT':
            return { prevValue: state.value, value: state.value + 1 };
        case 'ROLLBACK':
            return { ...state, value: state.prevValue };
        default:
            return state;
    }
}

function AdvancedCounter() {
    const [state, dispatch] = useReducer(reducer, initialState);

    const increment = () => {
        try {
            if (Math.random() < 0.3) throw new Error("Random failure");
            dispatch({ type: 'INCREMENT' });
        } catch {
            dispatch({ type: 'ROLLBACK' });
        }
    };

    return (
        <div>
            <p>Value: {state.value}</p>
            <button onCl ick={increment}>Increment</button>
        </div>
    );
}

Преимущество useReducer — централизованная обработка всех изменений состояния, что облегчает управление сложными rollback логиками.


Rollback и оптимистическое обновление

Часто применяются оптимистические обновления, когда UI изменяется до подтверждения успешного завершения операции (например, POST-запроса). В этом случае rollback стратегия является обязательной:

  1. Сохраняется предыдущее состояние.
  2. Выполняется обновление интерфейса.
  3. После ответа сервера определяется, нужно ли откатить изменения.

Пример оптимистического обновления с rollback:

const optimisticUpdate = async (newValue) => {
    setPrevValue(value);
    setValue(newValue);
    try {
        const response = await apiUpdate(newValue);
        if (!response.ok) throw new Error("API error");
    } catch {
        setValue(prevValue);
    }
};

Выводы по реализации

  • Rollback стратегии в Preact строятся вокруг сохранения предыдущего состояния.
  • Для простого состояния подходят useState, для сложного — useReducer.
  • Асинхронные операции требуют отдельной обработки ошибок и отката.
  • Оптимистические обновления без rollback могут приводить к неконсистентности UI.
  • Централизованная обработка изменений повышает стабильность и управляемость приложения.

Rollback стратегии позволяют создавать интерфейсы, устойчивые к ошибкам, и поддерживают предсказуемость поведения компонентов.