Назад

Custom Invoker Commands в React: межкомпонентная связь без контекстов и проп-дриллинга? ⚡️

Нативные Custom Invoker Commands в React: примеры command и onCommand, преимущества и ограничения подхода.

Custom Invoker Commands в React: межкомпонентная связь без контекстов и проп-дриллинга? ⚡️

Нативный Invoker Commands API (command и commandfor у <button>) уже активно раскатывается по браузерам. Он позволяет декларативно открывать диалоги или поповеры прямо из HTML без единой строчки JS:

<button command="show-modal" commandfor="my-dialog">Открыть</button>
<dialog id="my-dialog">...</dialog>

️Но в статье Sergio Xalambrí разбирается куда более интересный сценарий — Custom Invoker Commands.

Как это работает в React?

Вместо встроенных команд вроде show-modal мы можем передать кастомную команду (например, command="--custom-action"). Браузер сам отправит кастомное событие command на целевой элемент.

В React мы можем повесить обычный слушатель onCommand на целевой DOM-элемент:

function ActionButton({ targetId }: { targetId: string }) {
  return (
    <button command="--reset-counter" commandfor={targetId}>
      Сбросить счётчик
    </button>
  );
}

function Counter({ id }: { id: string }) {
  const [count, setCount] = useState(0);

  return (
    <div
      id={id}
      onCommand={(e) => {
        if (e.command === "--reset-counter") {
          setCount(0);
        }
      }}
    >
      <p>Счётчик: {count}</p>
    </div>
  );
}

Кнопка и элемент-приёмник могут находиться в абсолютно разных ветках React-дерева — для передачи команды не нужны ни глобальное состояние, ни Context API, ни проп-дриллинг.

➕ Плюсы подхода

  1. Разрыв зацепления (Decoupling): Кнопка отправки действия вообще ничего не знает про состояние компонента-цели. Ей нужно знать только id DOM-узла.
  2. Меньше ререндеров: В отличие от React Context или Redux/Zustand, клик по такой кнопке не вызывает каскадный перерендер родительских компонентов или дерева контекста. Работает нативный браузерный Event Dispatching.
  3. Работает до гидратации (Zero JS на отправку): Нажатие на кнопку генерирует событие на уровне DOM даже в процессе загрузки страницы. Как только React загидрирует целевой компонент и повесит onCommand, обработчик отработает.
  4. Стандартизация: Это не костыль или специфичный для React паттерн, а нативный специфицированный веб-стандарт.

➖ Минусы и узкие места

  1. Завязка на ID: commandfor требует жесткой привязки к id DOM-элемента. В React с компонентами с динамическим id (например, через useId()) придётся следить за корректной передачей идентификаторов.
  2. TypeScript & React Types: Так как спецификация свежая, типы React (ButtonHTMLAttributes) и JSX-событие onCommand могут требовать ручного доопределения (declare module 'react') в некоторых проектах.
  3. Строгий синтаксис: Названия кастомных команд обязаны начинаться с двух дефисов (--my-command), иначе браузер проигнорирует команду или выбросит ошибку.

Как вам идея использовать нативные браузерные эвенты вместо React Context для простых команд в UI?


Читать статью

Также рекомендую прочитать статью про имплементацию Markdown тулбара в React с использованием этого подхода — https://sergiodxa.com/tutorials/build-a-command-driven-markdown-toolbar-in-react

Скопировано