Обновление свойств

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

Реактивность свойств

Каждое свойство, определённое через props, автоматически становится реактивным. Это означает, что любое изменение значения свойства приводит к повторному рендеру компонента. Например:

import { c, useProp } from 'atomico';

function MyComponent({ count }) {
    const [localCount, setLocalCount] = useProp('count');
    return (
        <host>
            <button onCl ick={() => setLocalCount(localCount + 1)}>
                {localCount}
            </button>
        </host>
    );
}

MyComponent.props = {
    count: Number
};

customElements.define('my-component', c(MyComponent));

В этом примере useProp связывает локальное состояние с внешним свойством. Любое изменение count извне автоматически обновляет локальное состояние и вызывает перерисовку.

Механизм обновления

Обновление свойств в Atomico основано на наблюдении за изменениями объекта props через Proxy. Когда значение свойства меняется:

  1. Atomico определяет, какие свойства изменились.
  2. Компонент получает уведомление и запускается повторный рендер.
  3. Если есть эффекты, зависящие от свойств (useEffect с зависимостями), они также выполняются.
import { c, useProp, useEffect } from 'atomico';

function MyComponent({ name }) {
    const [currentName, setCurrentName] = useProp('name');

    useEffect(() => {
        console.log(`Свойство name изменилось: ${currentName}`);
    }, [currentName]);

    return <host>{currentName}</host>;
}

MyComponent.props = {
    name: String
};

useEffect с зависимостью [currentName] гарантирует, что функция сработает только при реальном изменении значения свойства.

Свойства по умолчанию и типизация

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

MyComponent.props = {
    name: {
        type: String,
        value: "Неизвестно"
    },
    count: {
        type: Number,
        value: 0
    }
};
  • type определяет тип данных, который ожидается.
  • value задаёт начальное значение, используемое до того, как свойство будет установлено извне.

Если новое значение не соответствует типу, Atomico пытается выполнить преобразование (например, строку в число). При несоответствии типу корректный рендер не гарантируется.

Синхронизация внешних и внутренних изменений

Atomico позволяет синхронизировать внешние изменения свойств с внутренним состоянием компонента через useProp. Внутренние изменения, выполненные через setLocalProp, автоматически отражаются во внешнем свойстве:

function Counter({ count }) {
    const [localCount, setLocalCount] = useProp('count');

    const increment = () => setLocalCount(localCount + 1);

    return <host><button onCl ick={increment}>{localCount}</button></host>;
}

Если родительский компонент изменяет count, localCount обновится автоматически. Если компонент сам меняет localCount, это изменение также отражается на внешнем свойстве.

Производительность обновлений

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

Для массивов и объектов рекомендуется создавать новые экземпляры при изменении, так как реактивность основана на ссылках:

const [items, setItems] = useProp('items');

// Правильно
setItems([...items, newItem]);

// Неправильно, не вызовет обновление
items.push(newItem);
setItems(items);

Свойства с отложенным рендером

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

MyComponent.props = {
    name: { type: String, reflect: false },
    visible: { type: Boolean, reflect: true }
};
  • reflect: true — значение свойства синхронизируется с атрибутом DOM.
  • reflect: false — внутренние изменения свойства не трогают DOM, но вызывают повторный рендер.

Работа с колбэками при обновлении

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

MyComponent.props = {
    count: {
        type: Number,
        watch: (oldValue, newValue) => {
            console.log(`count изменился с ${oldValue} на ${newValue}`);
        }
    }
};

watch вызывается после изменения значения, но до рендера. Это удобно для логирования или запуска побочных эффектов.

Интеграция с внешними данными

Свойства Atomico хорошо сочетаются с состоянием из внешних источников, таких как Redux, RxJS или обычные объекты. Для синхронизации достаточно использовать useProp совместно с подпиской на внешние данные:

import { fromEvent } from 'rxjs';

function InputComponent({ value }) {
    const [inputValue, setInputValue] = useProp('value');

    useEffect(() => {
        const subscription = fromEvent(document, 'input')
            .subscribe(event => setInputValue(event.target.value));
        return () => subscription.unsubscribe();
    }, []);

    return <host><input value={inputValue} /></host>;
}

Вывод

Обновление свойств в Atomico строится на реактивной синхронизации между внешними и внутренними данными, автоматическом перерендере и строгой типизации. Использование useProp, watch, reflect и правильная работа с объектами и массивами позволяют создавать эффективные и предсказуемые компоненты, легко интегрируемые с любыми внешними источниками данных.