Controlled components представляют собой компоненты, состояние которых полностью управляется извне через свойства (props). В отличие от uncontrolled компонентов, где внутреннее состояние управляется самим компонентом, controlled components полагаются на данные, переданные извне, и события, уведомляющие об изменениях. Такой подход обеспечивает единую точку управления состоянием и упрощает синхронизацию данных между компонентами.
Состояние управляется через свойства Компонент
принимает значение через @Prop() и не хранит его копию в
@State(). Любое изменение должно инициироваться внешним
кодом.
import { Component, Prop, h, Event, EventEmitter } from '@stencil/core';
@Component({
tag: 'controlled-input',
styleUrl: 'controlled-input.css',
shadow: true,
})
export class ControlledInput {
@Prop() value: string = '';
@Event() valueChange: EventEmitter<string>;
handleInput(event: Event) {
const input = event.target as HTMLInputElement;
this.valueChange.emit(input.value);
}
render() {
return <input value={this.value} onIn put={e => this.handleInput(e)} />;
}
}
В примере выше значение инпута полностью управляется
извне. Компонент лишь уведомляет о каждом изменении через
событие valueChange.
События как основной механизм синхронизации Controlled components должны испускать события при каждом изменении состояния. Это позволяет родительскому компоненту перехватывать изменения и обновлять пропсы.
<controlled-input value={this.parentValue} onValueCha nge={e => this.parentValue = e.detail}></controlled-input>
Здесь parentValue является источником правды, а
компонент controlled-input просто отражает это
значение.
Отсутствие локального состояния Любое хранение
состояния в @State() для контролируемых компонентов
противоречит принципу «единое место правды». Локальный
@State() может использоваться только для вспомогательных
данных, не влияющих на внешний API компонента.
Controlled components идеально подходят для работы с формами, где требуется единственное централизованное состояние:
@Component({
tag: 'form-example',
shadow: true
})
export class FormExample {
@State() username: string = '';
handleUsernameChange(value: string) {
this.username = value;
}
render() {
return (
<form>
<controlled-input value={this.username} onValueCha nge={e => this.handleUsernameChange(e.detail)} />
<button type="submit">Submit</button>
</form>
);
}
}
В этом примере ввод пользователя немедленно синхронизируется с состоянием родителя, обеспечивая предсказуемость и возможность валидации на лету.
Использование @Event() и
EventEmitter Для передачи изменений
компонентам-родителям обязательно использовать события. Прямое изменение
@Prop() невозможно, так как они только для
чтения.
Shadow DOM и рефы Если компонент использует
Shadow DOM, необходимо корректно проксировать события. В Stencil
события, испущенные через EventEmitter, автоматически
всплывают, что упрощает интеграцию.
Слияние с uncontrolled логикой Иногда нужно реализовать гибридный компонент, который может быть как controlled, так и uncontrolled. Для этого используют проверку на наличие пропа:
@Prop() value?: string;
@State() internalValue: string = '';
get currentValue() {
return this.value !== undefined ? this.value : this.internalValue;
}
handleInput(event: Event) {
const input = event.target as HTMLInputElement;
if (this.value === undefined) {
this.internalValue = input.value;
}
this.valueChange.emit(input.value);
}
Такой подход позволяет компоненту работать автономно, если родитель
не передал value.
<propName>Change для единообразия.Controlled components в Stencil обеспечивают ясное разделение обязанностей между компонентом и родителем, повышают надежность интерфейсов и делают код более поддерживаемым в масштабных приложениях.