Сила композиции компонентов в React

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

Прежде чем мы перейдем к React, вот фрагмент кода, я хотел бы знать, что вы думаете об этом интересном:

<select options={[
  {
    value: 'value1'
    label: 'label1'
  },
  {
    value: 'value2'
    label: 'label2'
  },
  {
    value: 'value3'
    label: 'label3'
  }]
} />

Ой! К счастью, это только что придумано, и на самом деле мы можем использовать select такой элемент:

<select>
  <option value="value1">label1</option>
  <option value="value2">label2</option>
  <option value="value3">label3</option>
</select>

На самом деле это очень распространенный пример композиции, и это отличный и простой API. Давайте теперь рассмотрим, как мы создаем компоненты в React. Давайте возьмем пример компонента Alert, который должен поддерживать несколько вариантов.

<Alert
  header="Success alert"
  variant="success"
  icon="success"
  description="This is a success alert message"
/>

<Alert
  header="Info alert"
  variant="info"
  icon="info"
  description="This is an info alert message"
/>

<Alert
  header="Error alert"
  variant="error"
  icon="error"
  description="This is an error alert message"
/>

Как насчет того, чтобы значок отображался на противоположной стороне? мы бы добавили iconPosition опора я думаю 🤔

<Alert
  header="Error alert"
  variant="error"
  icon="error"
  description="This is an error alert message"
  iconPosition="left"
/>

Если вы когда-либо создавали код, который использовался более чем в одном месте, то вы, вероятно, знакомы с этой историей:

  1. Вы создаете повторно используемый фрагмент кода (функция, компонент React или хук React и т. д.) и делитесь им (с коллегами или публикуете как OSS).
  2. Кто-то обращается к вам с новым вариантом использования, который ваш код не совсем поддерживает, но мог бы с небольшой настройкой.
  3. Вы добавляете аргумент/свойство/опцию в свой повторно используемый код и связанную с ним логику для поддержки этого варианта использования.
  4. Повторите шаги 2 и 3 несколько раз (или много раз 😬).
  5. Многоразовый код теперь превратился в кошмар для использования и поддержки 😭

(вышеуказанные шаги взяты из Статья Кента Додда)

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

Что такое компонентный состав?

Проще говоря, это просто означает объединение компонентов для формирования других компонентов. На практике это достигается в основном за счет children опора Я считаю, что это функция React, которая используется недостаточно, хотя она позволяет нам создавать гибкие компоненты, которыми приятно пользоваться.

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

<Alert status="error">
  <Icon />
  <Alert.Title>Your browser is outdated!</Alert.Title>
  <Alert.Description>Your Volunteer Hub experience may be degraded.</Alert.Description>
</Alert>

Это открывает гораздо больше возможностей. Мы можем легко расширить или переопределить функциональность, не меняя логику компонента. У нас больше контроля 🤓

export const Alert = ({ status, children }) => {
  return (
    <div className={status}>
      {children}
    </div>
  )
}

Alert.Title = function AlertTitle({ children }) {
  return (
    <span className="alert-title">{children}</span>
  )
}

Alert.Description = function AlertDescription({ children }) {
  return (
    <span className="alert-description">{children}</span>
  )
}

Инверсия контроля

Инверсия управления (IoC) — это шаблон проектирования, который просто означает (в контексте компонентов) перенос части управления с самого компонента туда, где он используется.

Возьмем другой пример Menu компонент. Обычный способ его создания примерно такой:

<Menu
  button={<button>Menu</button>}
  items={[
    {contents: 'Download', onSelect: () => console.log('Download')},
    {contents: 'Create a Copy', onSelect: () => console.log('Create a Copy')},
    {contents: 'Delete', onSelect: () => console.log('Delete')},
  ]}
/>

Что, если мы хотим добавить разделитель между некоторыми пунктами меню, чтобы они были сгруппированы? Возможно, мы можем сделать это с помощью дополнительного свойства объекта элемента, но это еще больше усложняет задачу.

При создании нового компонента стоит сначала потратить некоторое время на то, чтобы решить, как мы хотим его использовать, чтобы создать хороший API для других (или для себя в будущем), которые будут использовать этот компонент в будущем. В этом случае, что, если мы возложим часть ответственности за рендеринг на пользователя нашего компонента?

<Menu>
  <Menu.Button>Menu</Menu.Button>
  <Menu.List>
    <Menu.Item onSelect={() => console.log('Download')}>Download</Menu.Item>
    <Menu.Item onSelect={() => console.log('Copy')}>Create a Copy</Menu.Item>
    <Divider />
    <Menu.Item onSelect={() => console.log('Delete')}>Delete</Menu.Item>
  </Menu.List>
</Menu>

Его будет немного сложнее построить, чем компонент Alert, но конечный результат очень гибкий и стоит затраченных усилий.

Как мне этого добиться?

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

  • Использование Context API в родительском элементе для хранения общего состояния
  • Используя Детский API верхнего уровня (например, Children.mapили Children.forEach)

Куда мне идти отсюда?

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

Похожие записи

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *