Как стать автором
Обновить

Каррируем React-компоненты: функциональные паттерны на фронтенде

Уровень сложностиСредний
Время на прочтение4 мин
Количество просмотров5.8K

Современный React все больше соответствует идеалам функционального программирования.

Ежедневно мы пользуемся подходами из мира ФП, зачастую даже не подозревая об этом.

Эти паттерны плотно укоренились в сознании фронтенд-разработчиков, делая наш код значительно чище, читаемее и предсказуемее.

Вот лишь некоторые из них:

  • Декларативный код. Мы не говорим как рендерить, мы говорим что.

  • UI = f(state). Интерфейс  –  функция от состояния.

  • Композиция. Сложные компоненты собираются из простых независимых блоков.

  • Мемоизация. Функцию можно переиспользовать, пока данные не изменятся.

  • И так далее.

Фото David Clode
Фото David Clode

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

Обилие математических терминов, жесткие рамки и ограничения, непонятные формы записи и взрывающие голову рекурсии.

Но как только вы осознаете всю красоту и мощь функциональных паттернов  – вы уже не сможете без них обойтись.


Сегодня мы углубимся в идеи функционального мира и рассмотрим один из классических паттернов  –  каррирование. Мы разберемся с тем, что это такое и как применить эти знания в React.

Каррирование

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

function sum(a: number, b: number) {
  return a + b;
}

function curriedSum(a: number) {
  return function(b: number) {
    return a + b;
  };
}

curriedSum(2)(3);

Часто вместе с каррированием обсуждается "частичное применение"  –  фиксирование части аргументов заранее.

Эти паттерны схожи, но не идентичны.

При каррировании каждый вызов может принимать строго один аргумент, а при частичном применении  –  несколько.

А что в React?

В современной React-разработке компонент в виде функции является стандартом. Он принимает пропсы на вход и возвращает JSX на выходе.

Можем ли мы применить принцип каррирования к props React-компонента? Зафиксировать часть значений, а остальные передать позднее.

В этой статье мы реализуем именно принцип частичного применения. Передавать один проп или несколько  –  решать вам.

Предлагаю отложить все объяснения на потом и сразу взглянуть пример:

import { FC } from 'react';

function partialProps<P extends Record<string, unknown>, K extends keyof P>(
  Component: FC<P>,
  partial: Pick<P, K>,
): FC<Omit<P, K>> {
  return (props) => {
    return <Component {...partial} {...(props as P)} />;
  };
}

Теперь давайте подробнее рассмотрим из чего состоит данная функция:

  • P  –  общий тип пропов исходного компонента.

  • K extends keyof P  – множество ключей пропов, которые мы хотим зафиксировать.

  • Pick<P, K> берёт из P только ключи из K.

  • Omit<P, K> удаляет из P все ключи из K.

  • Возвращаемым результатом этой функции будет новый компонент FC<Omit<P, K>>, который ожидает только оставшиеся поля.

  • Во время рендера partial и props будут склеены в общий набор пропов, соответствующий типу P.

Вот так просто у нас получилась функция частичного применения для пропов React-компонента.

Как по мне  –  выглядит очень лаконично, вполне в духе ФП!


Теперь пришло время рассмотреть несколько примеров того, как применить этот паттерн на практике.

Начнем с того, что перепишем изначальный пример с суммой двух чисел с использованием функции partialProps:

type Props = {
  a: number;
  b: number;
};

const Sum = ({ a, b }: Props) => {
  return <div>{a + b}</div>;
};

const PartialSum = partialProps(Component, { a: 1 });

export default function App() {
  return (
    <div>
      <PartialSum b={3} />
    </div>
  );
}

Как мы видим, компонент Sum больше не ждет, что мы передадим ему все необходимые значения сразу, а готов принимать их последовательно.

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

Цвет, размер, вариант  –  все это может быть сконфигурированно заранее с помощью функции partialProps.

type ButtonProps = {
  color: string;
  size: "small" | "medium" | "large";
  variant: "contained" | "outlined";
  onClick?: () => void;
};

const Button: React.FC<ButtonProps> = ({
  color,
  size,
  variant,
  onClick,
  children,
}) => {
  const style = { backgroundColor: color };
  const className = `${size} ${variant}`;
  
  return (
    <button
      style={style}
      onClick={onClick}
      className={className}
    >
      {children}
    </button>
  );
};

const ImportantRedButton = partialProps(Button, {
  color: "red",
  size: "large",
  variant: "contained",
});

export default function App() {
  return (
    <ImportantRedButton onClick={() => alert("Clicked!")}>
      Delete
    </ImportantRedButton>
  );
};

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

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

  • Предустановленный роутер / навигация.

  • Предварительно заполненные параметры API-запроса.

  • Предконфигурированные поля формы.

  • Преднастроенные визуальные темы.

  • Частичное применение параметров коллбэков.

Фото Tiard Schulz
Фото Tiard Schulz

Higher-Order Components

Если вы давно программируете на React, наверно, смогли заметить, что этот подход во многом совпадает по принципу с другим паттерном (кстати тоже из функционального программирования) — HOC (Higher‑Order Component).

Когда вы каррируете компонент — вы фактически создаете узкоспециализированный HOC, который подставляет пропсы.

С точки зрения реализации частичное применение пропсов — это частный случай HOC.

Я бы сказал, что тут нет конфликта, каждый решает свою специфическую задачу.

Однако у такого подхода есть и свои плюсы:

  • Простота и наглядность. Нет «магии» или сторонних эффектов.

  • Чистота. Нет необходимости явно указывать принимаемые параметры, как в HOC.

  • Строгая типизация. TypeScript сможет точно вывести какие поля остались свободными, а какие уже зафиксированы.

Заключение

Частичное применение и каррирование — это отличные паттерны, которые прекрасно ложатся на функциональную парадигму программирования в React!

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

Теги:
Хабы:
Всего голосов 7: ↑7 и ↓0+10
Комментарии3

Публикации

Работа

Ближайшие события