Современный 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!

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

Комментарии (2)


  1. capfsb
    12.02.2025 03:41

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

    function ImportantRedButton(props) {
        return <Button
          color="red"
          size="large"
          variant="contained"
          {...props} 
        />;
      }


    1. AndreyVolfman Автор
      12.02.2025 03:41

      Хорошая альтернатива, благодарю!