Контролируемые компоненты

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

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

В SkateJS свойства компонента объявляются через функцию props. Каждое свойство может иметь:

  • Тип — указывается через конструктор (String, Number, Boolean, Object, Array).
  • Значение по умолчанию через default.
  • Функцию обработки (set), которая вызывается при изменении значения.

Пример объявления свойств:

import { define, props, Component } from 'skatejs';

class ControlledInput extends Component {
  static get props() {
    return {
      value: props.string({ default: '' }),
      disabled: props.boolean({ default: false })
    };
  }

  renderCallback() {
    return (
      <input 
        value={this.value} 
        disabled={this.disabled} 
        onIn put={e => this._onInput(e)} 
      />
    );
  }

  _onInput(event) {
    this.value = event.target.value;
  }
}

define('controlled-input', ControlledInput);

Здесь свойство value делает компонент контролируемым. Любые изменения внешнего свойства value автоматически обновляют отображаемое значение в <input>.

Обработка изменений свойства

Для контролируемых компонентов важно различать внутренние изменения состояния и внешние изменения свойства. SkateJS предоставляет механизм наблюдателей (observers) через опцию set в объявлении свойства:

static get props() {
  return {
    value: props.string({
      default: '',
      set(newValue, oldValue) {
        if (newValue !== oldValue) {
          console.log(`value изменилось с ${oldValue} на ${newValue}`);
        }
        return newValue;
      }
    })
  };
}

set позволяет выполнять дополнительные действия при обновлении свойства, но при этом возвращаемое значение становится фактическим значением свойства.

События для синхронизации

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

_onInput(event) {
  const newValue = event.target.value;
  this.dispatchEvent(new CustomEvent('value-change', {
    detail: newValue,
    bubbles: true,
    composed: true
  }));
}

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

Контролируемые vs неконтролируемые компоненты

Неконтролируемый компонент хранит внутреннее состояние и может изменять его без внешнего вмешательства. Контролируемый компонент:

  • Не хранит состояния независимо — все ключевые данные приходят извне через свойства.
  • Синхронизируется с родителем — любые внутренние изменения должны транслироваться наружу через события.
  • Обеспечивает предсказуемое поведение — состояние компонента всегда известно внешнему коду.

Пример комбинированного подхода:

class MixedInput extends Component {
  static get props() {
    return {
      value: props.string(),
      defaultValue: props.string({ default: '' })
    };
  }

  constructor() {
    super();
    this._internalValue = this.defaultValue;
  }

  renderCallback() {
    const displayValue = this.value !== undefined ? this.value : this._internalValue;
    return (
      <input 
        value={displayValue} 
        onIn put={e => this._onInput(e)} 
      />
    );
  }

  _onInput(event) {
    const newValue = event.target.value;
    if (this.value === undefined) {
      this._internalValue = newValue; // неконтролируемое поведение
    } else {
      this.dispatchEvent(new CustomEvent('value-change', {
        detail: newValue,
        bubbles: true,
        composed: true
      }));
    }
  }
}

define('mixed-input', MixedInput);

В этом примере компонент работает как контролируемый, если есть значение value, и как неконтролируемый — если его нет.

Связь с жизненным циклом

Контролируемые компоненты активно используют жизненный цикл SkateJS для отслеживания изменений:

  • connectedCallback — настройка слушателей, начальная инициализация.
  • updatedCallback — реакция на изменение свойств и синхронизация с DOM.
  • disconnectedCallback — очистка ресурсов и событий.

Пример использования updatedCallback для синхронизации:

updatedCallback(oldProps) {
  if (oldProps.value !== this.value) {
    console.log('Внешнее свойство value изменилось');
  }
}

Практические советы

  • Использовать контролируемые компоненты для форм, сложных UI-компонентов и виджетов, где внешний код должен иметь полный контроль.
  • Всегда синхронизировать внутренние события с внешними свойствами через кастомные события.
  • Для комбинированного поведения использовать внутренние свойства только при отсутствии внешнего значения.

Контролируемые компоненты в SkateJS обеспечивают чёткую архитектуру данных, предсказуемость и удобство интеграции с другими библиотеками и фреймворками. Они идеально подходят для построения сложных интерфейсов с динамическим состоянием и строгим контролем поведения.