Композиция и наследование компонентов

Inferno — высокопроизводительный фреймворк для построения интерфейсов на JavaScript, обеспечивающий виртуальный DOM и оптимизированное обновление пользовательского интерфейса. Одной из ключевых особенностей разработки на Inferno является работа с компонентами, а именно их композиция и наследование, что позволяет создавать гибкие и повторно используемые структуры интерфейса.


Компоненты в Inferno

Компоненты в Inferno бывают двух типов:

  1. Функциональные компоненты — простые функции, принимающие props и возвращающие виртуальный DOM. Они не имеют собственного состояния (до появления хуков) и являются идеальными для отображения статических данных или чистых представлений.

  2. Классовые компоненты — наследуются от Inferno.Component и предоставляют доступ к жизненному циклу, состоянию (state) и методам для управления поведением компонента.

Пример классового компонента:

import { Component } from 'inferno';

class Button extends Component {
  constructor(props) {
    super(props);
    this.state = { clicked: false };
  }

  handleClick = () => {
    this.setState({ clicked: true });
  }

  render() {
    return (
      <button onCl ick={this.handleClick}>
        {this.state.clicked ? 'Clicked' : 'Click me'}
      </button>
    );
  }
}

Принцип композиции компонентов

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

Ключевые моменты композиции:

  • Компоненты должны быть независимыми и максимально атомарными.
  • Передача данных осуществляется через props.
  • Вложенные компоненты могут использовать дочерние элементы через props.children.

Пример композиции:

const Card = ({ title, children }) => (
  <div className="card">
    <h2>{title}</h2>
    <div className="card-content">{children}</div>
  </div>
);

const App = () => (
  <Card title="Пример карточки">
    <p>Содержимое карточки</p>
  </Card>
);

В этом примере компонент Card выступает контейнером для произвольного содержимого, передаваемого через children. Такой подход позволяет строить сложные интерфейсы, не создавая монолитных компонентов.


Наследование компонентов

Inferno поддерживает классическое наследование, позволяя создавать компоненты, расширяющие функциональность базового компонента. Наследование используется, когда требуется добавление специализированного поведения к общему компоненту без дублирования кода.

Пример наследования:

class BaseButton extends Component {
  render() {
    return <button>{this.props.label}</button>;
  }
}

class ColoredButton extends BaseButton {
  render() {
    return <button style={{ color: this.props.color }}>{this.props.label}</button>;
  }
}

В данном примере ColoredButton расширяет базовый BaseButton, добавляя возможность задавать цвет текста через props. При этом сохраняется исходная структура и поведение базового компонента.

Рекомендации по наследованию:

  • Использовать наследование только для компонентов с явно выраженной общей логикой.
  • Предпочтительнее применять композицию для объединения разных компонентов, поскольку это упрощает тестирование и поддержку.
  • Методы жизненного цикла (componentDidMount, componentDidUpdate) можно переопределять с вызовом super, чтобы сохранить поведение родителя.

Передача состояния и props при композиции и наследовании

Композиция и наследование тесно связаны с управлением состоянием:

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

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

class Toggle extends Component {
  constructor(props) {
    super(props);
    this.state = { on: false };
  }

  toggle = () => {
    this.setState({ on: !this.state.on });
  }

  render() {
    return this.props.render(this.state.on, this.toggle);
  }
}

const App = () => (
  <Toggle render={(on, toggle) => (
    <button onCl ick={toggle}>
      {on ? 'Включено' : 'Выключено'}
    </button>
  )}/>
);

Здесь компонент Toggle управляет состоянием, а через композицию дочерний компонент отвечает только за отображение.


Использование HOC (Higher-Order Components) для композиции

Inferno поддерживает подход Higher-Order Components (HOC), который позволяет создавать компоненты, оборачивающие другие компоненты для добавления функциональности.

Пример HOC:

const withLogger = (WrappedComponent) => {
  return class extends Component {
    componentDidMount() {
      console.log('Компонент смонтирован');
    }

    render() {
      return <WrappedComponent {...this.props} />;
    }
  };
};

const ButtonWithLogger = withLogger(Button);

Такой подход позволяет обогащать компоненты поведением без изменения их исходного кода, сохраняя принципы композиции и повторного использования.


Итоговая структура подходов

  • Композиция: объединение независимых компонентов, управление данными через props и children.
  • Наследование: расширение функциональности базового компонента через классическое наследование.
  • HOC: внедрение дополнительной логики, оборачивая существующие компоненты.

Эти методы дают возможность строить сложные интерфейсы в Inferno, сохраняя чистоту кода, повторное использование и управляемость состояния компонентов.