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

Я буду использовать довольно простой компонент Button для иллюстрации. Раньше мы проектировали компонент по следующим принципам:

interface ButtonProps {
 inverted?: boolean
 disabled?: boolean;
 onClick: () => void;
}
export function Button(props: ButtonProps) {
 return <button … />
}

Обратите внимание, что в интерфейсе перечислено очень мало свойств. Их можно разделить на:

  • Свойства, которые используются для внутренних целей, для описания настраиваемого поведения или стиля (`инвертированный`)
  • Свойства, которые перенаправляются в базовый элемент HTML (`disabled`,` onClick`)

Базовый элемент HTML (`‹button›`) поддерживает множество других свойств, которые мы не включили. Кроме того, хотя наше определение свойства onClick технически совместимо с атрибутом onClick элемента ‹button›, оно не включает аргумент события, что делает функцию менее полезной в некоторых ситуациях.

Мы исключили базовые реквизиты `‹button›` по двум причинам:

  1. В конкретном проекте они нам не понадобились. HTML-элементы имеют много реквизита, в том числе и все они в интерфейсе будут потрачены впустую.
  2. Мы явно не хотели включать определенные свойства (например, `style`) в целях поощрения передовых методов (например, использования CSS вместо встроенных стилей).

Первый вопрос связан с ленью, и его можно решить с помощью более совершенных инструментов. Вернее, с лучшим пониманием наших существующих инструментов! Определения типов React содержат типы, которые описывают свойства всех элементов HTML, и мы должны их использовать:

interface ButtonProps extends React.ComponentProps<”button”> {
  inverted?: boolean;
}

С этим определением компонент принимает все те же свойства, что и `‹button›`, плюс наши собственные. Нам просто нужно убедиться, что все дополнительные свойства переданы в базовый элемент HTML.

export function Button({ inverted, …props }: ButtonProps) {
  return <button {…props} />
}

Для большинства компонентов, которые мы пишем, мы можем определить, каким будет базовый элемент HTML. Очень часто это «‹div›«, но также может быть «‹button›« или «‹input›« и т. Д. Наши компоненты должны четко указывать, что является корневым элементом, и принимать все их свойства.

Вторая причина для исключения свойств, на мой взгляд, ошибочна. Система типов - неподходящее место для применения передовых практик. Лучшие практики часто мешают экспериментам и препятствуют игровым исследованиям.

По той же причине, по которой я иногда создаю коммиты Git с такими сообщениями, как «…» или «WIP», я хочу иметь возможность писать весь грязный уродливый код, который мне нужен для изучения идей. Да, я знаю, что это плохой код, но поверьте, я очищу его, прежде чем отправлять на GitHub.

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

Наконец, если это сработает, это не глупо.

Линтеры (~ автоматизированные инструменты) и обзоры кода (~ люди) - гораздо лучшие места для применения передовых методов. Хотя бы по той простой причине, что вы можете отключить их или обсудить код со своими коллегами, если вы действительно чувствуете, что правила не должны применяться в вашей конкретной ситуации.

Я еще не говорил о ссылках, но важно, чтобы любой компонент, отображающий определенный элемент HTML, также принимал ссылку и пересылал ее элементу.

Как это выглядит на практике? При создании нового компонента выполните следующие шаги:

  1. Решите, что является базовым элементом HTML.
  2. Запишите интерфейс, расширяющий `React.ComponentPropsWithRef‹… ›`.
  3. Определите компонент с помощью React.forwardRef () и перенаправьте ссылку, а также любые дополнительные свойства в базовый элемент HTML.
interface ButtonProps extends React.ComponentPropsWithRef<”button”> {
  inverted?: boolean;
}
export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(function Button({ inverted, …props }, ref) {
  return <button ref={ref} {…props} />
});

Иногда вам может потребоваться изменить базовый элемент HTML. Часто это делается по семантическим причинам. Хороший пример - если вы хотите иметь ссылку (`‹a›`), которая выглядит как кнопка. Material-UI неплохо справляется, все его компоненты имеют для этого единый API:

import { Button } from “@material-ui/core”
<Button component=”a” href=”/home”>Click Me!</Button>

Поддержать это в системе типов непросто. Тип базового элемента HTML изменяет типы свойств, которые принимает компонент. Когда вы используете `‹Button›`, он должен принимать все реквизиты `‹button›`, но когда вы используете `‹ Button component = «a» `›, он должен принимать все реквизиты `‹a›` (например, `href`).

Как это красиво выразить в системе типов - это материал для другой статьи. А пока желаю удачного кодирования.