JS: React

Теория: Вложенные компоненты

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

Один из способов компоновки компонентов вам уже известен — children. Причём нет никакой разницы, являются ли потомки встроенными в React компонентами или это отдельно написанные компоненты.

class Alert extends React.Component {
  render() {
    const { children } = this.props
    return (
      <div className="alert alert-primary">
        {children}
      </div>
    )
  }
}

const vdom = (
  <Alert>
    <p>Paragraph 1</p>
    <hr />
    <p className="mb-0">Paragraph 2</p>
  </Alert>
)

const root = ReactDOM.createRoot(document.getElementById('react-root'))
root.render(vdom)

В некоторых ситуациях внутрь компонента нужно передавать только определённые, специально созданные под него компоненты. Например, компонент Card до текущего момента был реализован так, что он на вход мог принимать только пропсы:

class Card extends React.Component {
  render() {
    const { title, text } = this.props
    return (
      <div className="card">
        <div className="card-body">
          <h4 className="card-title">{title}</h4>
          <p className="card-text">{text}</p>
          <button type="button" className="btn btn-primary">Go somewhere</button>
        </div>
      </div>
    )
  }
}

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

<Card>
  <CardImgTop src="path/to/image" />
  <CardBody>
    <CardTitle>Body</CardTitle>
  </CardBody>
</Card>

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

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

class Item extends React.Component {
  render() {
    const { value } = this.props;
    return <li><b>{value}</b></li>;
  }
}

class List extends React.Component {
  render() {
    const { items } = this.props;
    return <ul>
      {items.map((i) =>
        <Item key={i} value={i} />
      )}
    </ul>;
  }
}

const mountNode = document.getElementById('react-root');
const root = ReactDOM.createRoot(mountNode);
root.render(<List items={[1, 2, 3, 4, 5]} />);

Попрактиковаться

Вкладывать можно сколько угодно раз и какие угодно компоненты. Но здесь кроется одна опасность. Желание построить идеальную архитектуру толкает разработчиков заранее планировать то, как разбить приложение на компоненты и сразу их реализовать. Важно понимать, что вложенность сама по себе — это усложнение понимания, так как придётся постоянно прыгать из одного компонента в другой. Кроме того, жёсткая структура свяжет вас по рукам и ногам, рефакторинг просто так не сделаешь, и желание его делать сильно поубавится из-за любви к своему решению. Будьте прагматичны. Оптимальный путь добавлять новые компоненты — это следить за моментом, когда вам становится сложно в текущем компоненте из-за объёмов и количества переменных, с которыми приходится иметь дело одномоментно. И даже в этом случае часто достаточно выделить дополнительные функции рендеринга внутри самого компонента, например так: renderItem.

Состояние

Один из самых частых вопросов у тех, кто только начинает знакомиться с React, связан с тем, как распределять состояние по компонентам. Короткий ответ: никак. Почти во всех ситуациях разделение состояния усложнит код и работу с ним. Правильный подход — создать корневой компонент, который содержит всё состояние внутри себя, а все нижележащие компоненты получают свои данные как пропсы. Само состояние должно быть максимально плоским, как реляционная база данных. Тогда можно спокойно применять нормализацию и безболезненно выполнять обновления.

Иногда могут возникать ситуации, когда необходимые в глубине свойства приходится протаскивать сквозь множество промежуточных компонентов, которые сами эти пропсы не используют. Это еще одна причина стараться не увлекаться глубокой вложенностью. С другой стороны, в следующем курсе вы познакомитесь с Redux, который во многом решает эту проблему (и много других).

Колбеки

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

class Item extends React.Component {
  render() {
    const { value, onRemove } = this.props;
    return (
      <li>
        <a href="#" onClick={onRemove(value)}>{value}</a>
      </li>
    );
  }
}

class List extends React.Component {
  constructor(props) {
    super(props);
    const { items } = this.props;
    this.state = { items };
  }
  handleRemove = (value) => (e) => {
    e.preventDefault();
    const newItems = this.state.items.filter(item => item !== value);
    this.setState({ items: newItems });
  };

  render() {
    const { items } = this.state;
    return (
      <ul>
        {items.map(i =>
          <Item key={i} onRemove={this.handleRemove} value={i} />
        )}
      </ul>
    );
  }
}

const mountNode = document.getElementById('react-root');
const root = ReactDOM.createRoot(mountNode);
root.render(<List items={[1, 2, 3, 4, 5]} />);

Попрактиковаться

Завершено

0 / 30