Двусторонняя связь

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

Свойство prop и реактивность

Основной механизм передачи данных в Atomico — это свойства компонента (props). Свойства объявляются через функцию props и могут быть реактивными:

import { c, useProp } from "atomico";

function MyComponent() {
    const [value, setValue] = useProp("value", "");

    return (
        <input
            value={value}
            onIn put={event => setValue(event.target.value)}
        />
    );
}

MyComponent.props = {
    value: String
};

export default c(MyComponent);

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

  • useProp("value", "") создаёт связку между внутренним состоянием компонента и переданным свойством.
  • Любое изменение value через setValue автоматически отражается в DOM, а изменения извне компонента синхронизируются с внутренним состоянием.

Ключевой момент: useProp обеспечивает двустороннюю реактивность, где изменения в компоненте и изменения внешних данных остаются синхронизированными.

Связывание с пользовательским вводом

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

function Checkbox({ checked, onChange }) {
    return (
        <input
            type="checkbox"
            checked={checked}
            onCha nge={e => onChange(e.target.checked)}
        />
    );
}

Для интеграции с Atomico можно использовать useProp:

function ControlledCheckbox() {
    const [checked, setChecked] = useProp("checked", false);

    return <Checkbox checked={checked} onCha nge={setChecked} />;
}

Автоматическая синхронизация атрибутов

Atomico автоматически синхронизирует свойства компонента с HTML-атрибутами, что упрощает интеграцию с другими библиотеками и стандартными элементами DOM. При изменении значения свойства в компоненте соответствующий атрибут HTML обновляется и наоборот.

<MyComponent value="initial" />

Если внутри компонента произойдет изменение через setValue, <my-component> получит обновлённый атрибут value. Это особенно важно для создания настраиваемых элементов, которые должны работать как обычные HTML-теги.

Использование useState и вычисляемых свойств

Для сложных случаев двусторонней связи часто используют комбинацию useState и useProp. useState отвечает за локальное состояние, а useProp — за синхронизацию с внешними данными:

import { c, useProp, useState } from "atomico";

function Counter() {
    const [count, setCount] = useProp("count", 0);
    const [double, setDouble] = useState(count * 2);

    useEffect(() => {
        setDouble(count * 2);
    }, [count]);

    return (
        <div>
            <button onCl ick={() => setCount(count - 1)}>-</button>
            <span>{count}</span>
            <button onCl ick={() => setCount(count + 1)}>+</button>
            <p>Double: {double}</p>
        </div>
    );
}

Counter.props = {
    count: Number
};

export default c(Counter);

Особенности:

  • useProp гарантирует, что внешнее свойство count всегда синхронизировано с внутренним состоянием.
  • Локальное состояние double вычисляется на основе count, обеспечивая реактивное обновление без необходимости вмешательства извне.

Передача событий родителю

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

function Input({ value, onChange }) {
    return (
        <input
            value={value}
            onIn put={e => onChange(e.target.value)}
        />
    );
}

function App() {
    const [text, setText] = useState("hello");

    return <Input value={text} onCha nge={setText} />;
}

Atomico позволяет создавать и прослушивать пользовательские события через dispatch, что расширяет возможности двусторонней связи:

import { c, useProp, useHost } from "atomico";

function TextField() {
    const [value, setValue] = useProp("value", "");
    const host = useHost();

    const handleInput = event => {
        setValue(event.target.value);
        host.dispatch("value-changed", { detail: event.target.value });
    };

    return <input value={value} onIn put={handleInput} />;
}

Здесь:

  • dispatch("value-changed", { detail }) уведомляет родителя об изменении значения.
  • Родитель может подписаться на событие и обновлять своё состояние, сохраняя синхронизацию.

Ограничения и рекомендации

  • Свойства, передаваемые через props, лучше делать простыми типами (строки, числа, булевы значения), чтобы избежать сложной сериализации.
  • Для объектов и массивов рекомендуется использовать useState внутри компонента и синхронизировать изменения через события.
  • Двусторонняя связь должна быть контролируемой, иначе возможно расхождение состояния между компонентом и внешними данными.

Итоговая структура двусторонней связи

  1. Определение свойства через props.
  2. Использование useProp для реактивного состояния.
  3. Обработка пользовательских событий (onInput, onChange).
  4. При необходимости использование dispatch для уведомления родителя.
  5. Локальное состояние через useState для производных данных.

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