Механизм отслеживания изменений

Atomico — это библиотека для создания веб-компонентов с реактивным поведением. Ключевой элемент её архитектуры — механизм отслеживания изменений состояния, который позволяет компонентам автоматически обновлять представление при изменении данных. В этой статье рассматриваются основные принципы работы реактивности в Atomico, способы её настройки и оптимизации.


Референсные объекты и реактивность

В основе реактивной системы лежат реактивные объекты. Любое состояние компонента, которое должно быть отслеживаемым, создаётся через useState или useRef:

import { useState, html, component } from "atomico";

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

    return html`
        <div>
            <p>Счёт: ${count}</p>
            <button oncl ick=${() => setCount(count + 1)}>Увеличить</button>
        </div>
    `;
}

export default component(Counter);

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

  • useState возвращает массив [значение, функция изменения].
  • Любое изменение значения через setCount автоматически инициирует перерендер компонента.
  • Atomico отслеживает только значения, созданные через реактивные хуки. Прямое изменение обычных переменных не вызывает обновления.

Отслеживание изменений объектов и массивов

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

const [state, setState] = useState({ items: [] });

// Добавление элемента
setState({
    ...state,
    items: [...state.items, "новый элемент"]
});

Важно:

  • Использование иммутабельных операций (spread, slice) гарантирует корректное срабатывание перерендеринга.
  • Прямое изменение вложенных свойств (state.items.push(...)) не инициирует обновление.

События и эффектные хуки

Для отслеживания побочных эффектов применяются хуки useEffect и useEvent:

useEffect(() => {
    console.log("Состояние изменилось:", state.items);
}, [state.items]);
  • useEffect вызывается после каждого перерендеринга, если изменились указанные зависимости.
  • Зависимости — это реактивные значения, отслеживаемые Atomico.
  • Можно использовать useEvent для регистрации событий, реагирующих на конкретные изменения состояния.

Отслеживание изменений через useRef

useRef позволяет хранить данные, которые не должны автоматически инициировать ререндер, но могут быть реактивными при необходимости:

const countRef = useRef(0);

function increment() {
    countRef.current++;
    // Для триггера рендера нужно вызвать setState или useUpdate
}

Ключевые отличия useRef от useState:

  • useRef хранит мутируемое значение, которое не вызывает автоматический ререндер.
  • Используется для хранения ссылок на DOM-элементы или счётчиков, где обновление UI не требуется при каждом изменении.

Оптимизация рендеринга

Atomico применяет эффективный алгоритм сравнения виртуального DOM, поэтому перерендер происходит только при реальных изменениях состояния. Для оптимизации рекомендуется:

  1. Минимизировать вложенные объекты. Чем меньше глубина структуры, тем проще библиотеке определить изменения.

  2. Использовать иммутабельные операции. Любое изменение объекта должно создавать новый объект.

  3. Разделять состояние на независимые хуки. Каждое отдельное состояние через useState отслеживается отдельно, что снижает нагрузку на рендеринг.


Связь реактивности и шаблонов

Atomico интегрирует реактивные значения напрямую в шаблоны через html:

html`
    <ul>
        ${state.items.map(item => html`<li>${item}</li>`)}
    </ul>
`;
  • Любое изменение state.items автоматически пересоздаёт список <li>.
  • Рендеринг вложенных компонентов также реагирует на изменения их собственных реактивных состояний.

Отслеживание сложных состояний

Для комплексных компонентов с множеством взаимозависимых состояний используется комбинация useState, useRef и useEffect. Пример:

function TodoList() {
    const [todos, setTodos] = useState([]);
    const lastAdded = useRef(null);

    useEffect(() => {
        if (lastAdded.current) {
            console.log("Добавлена новая задача:", lastAdded.current);
        }
    }, [todos]);

    function addTodo(text) {
        const newTodo = { text, done: false };
        lastAdded.current = newTodo;
        setTodos([...todos, newTodo]);
    }

    return html`
        <div>
            <button oncl ick=${() => addTodo("Новая задача")}>Добавить</button>
            <ul>
                ${todos.map(todo => html`<li>${todo.text}</li>`)}
            </ul>
        </div>
    `;
}
  • useRef хранит последний добавленный объект без лишнего ререндеринга.
  • useEffect реагирует на изменение todos, фиксируя точный момент добавления нового элемента.

Механизм отслеживания изменений в Atomico обеспечивает гибкую и предсказуемую реактивность. Он опирается на реактивные хуки, иммутабельные структуры и эффективный алгоритм сравнения виртуального DOM, что позволяет создавать компоненты с минимальной нагрузкой и предсказуемым поведением при любых изменениях состояния.