Логика отображения «No-Op» для компонентов React: некоторые параметры

Скажем, у нас есть приложение React, которое отображает гиперссылки. Наш компонент Hyperlink может выглядеть следующим образом:

const Hyperlink = ({ url }) => (
  return <a href={url}>{url}</a>;
);

Теперь предположим, что родительский компонент этого Hyperlink является строкой таблицы, где у нас не всегда есть url. В некоторых функциях «сопоставления строк», где наша цель — получить данные и вернуть элемент строки таблицы, у нас может быть следующее:

rows.map(({ name, url }: { name: string, url?: string }) => {
  return (
    <tr>
      <td>
        <span>{name}</span>
      </td>
      <td>
        <Hyperlink url={url} />
      </td>
    </tr>
  );
}

Здесь свойство url является необязательным. Где мы справляемся с этим?

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

<td>
  {!url ? null : <Hyperlink url={url} />}
</td>

В качестве альтернативы за это может отвечать Hyperlink.

const Hyperlink = ({ url }: { url?: string }) => (
  return !url ? null : <a href={url}>{url}</a>;
);

Какой вариант более правильный?

Что ж, к сожалению, и, как и в случае с большинством технических проблем, это зависит от обстоятельств. Принимая во внимание принцип наименьшего удивления, вы можете быть склонны никогда не возвращать null из Hyperlink. Сохранение презентационных компонентов непонятными, как правило, является лучшей практикой. Но разбрызгивание каждого использования Hyperlink тернарным кодом, который вместо этого может возвращать null, звучит повторяющимся и утомительным в обслуживании. А что, если эта логика усложнится? Что, если нам нужны name и url? Конечно, любой уровень сложности здесь быстро раздует код, пытающийся использовать этот компонент.

Другой вариант — использовать компонент-оболочку, единственная цель которого — обрабатывать потенциально неопределенные параметры и определять, следует ли отображать базовый компонент. Это может выглядеть следующим образом:

const WrappedHyperlink = ({ url }: { url?: string }) => (
  return !url ? null : <Hyperlink url={url} />
);
// ...
<WrappedHyperlink url={undefined} /> // renders `null`

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

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

function getHyperlink(url?: string): JSX.Element | null {
  return !url ? null : <Hyperlink url={url} />;
);

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

<>
  {/* ... */}
  { getHyperlink(url) /* may or may not render a `Hyperlink` */ } </>

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

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