Как правильно обернуть компонент React, чтобы фиксировать события DOM, такие как щелчок, отправка и т. д., При пересылке дочерних элементов и ссылок в Typescript?

Я хотел бы добавить события отслеживания аналитики ко всем кнопкам в моем приложении. Все кнопки muiкнопки, но я чувствую, что моя проблема была бы такой же, если бы я просто использовал собственный HTML button.

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

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

Компонент выглядит так:

CustomButton.tsx

import _ from 'lodash';
import { Button, ButtonProps } from '@mui/material';
import { analyticsTrack, mergeRefs } from 'utils/functions';
import { forwardRef, ForwardedRef, createRef } from 'react';

interface CustomButtonProps extends ButtonProps {
  trackingName?: string;
}

const CustomButton = forwardRef(
  (
    { trackingName, children, ...rest }: CustomButtonProps,
    ref: ForwardedRef<any>
  ) => {
    const buttonRef = createRef();
    function handleClick(...args: any) {
      analyticsTrack('Button Clicked', {
        props: rest,
        ariaLabel: rest['aria-label'],
        trackingName,
        buttonText: typeof children === 'string' ? children : '',
      });
      if (rest.onClick) {
        rest.onClick(args);
      }
    }

    return (
      // @ts-ignore
      // eslint-disable-next-line react/jsx-no-bind
      <Button
        {..._.omitBy(rest, ['onClick'])}
        onMouseDown = {(event) => event.stopPropagation()}
        onTouchStart = {(event) => event.stopPropagation()}
        onClick = {(event) => {
          event.stopPropagation();
          event.preventDefault();
          handleClick();
          // @ts-ignore
          buttonRef.current && buttonRef.current.dispatchEvent(event);
        }}
        // @ts-ignore
        ref = {mergeRefs(ref, buttonRef)}
      >
        {children}
      </Button>
    );
  }
);

export default CustomButton;

Я также очень запутался в типах, не совсем понимаю, почему существует разница между типами ForwardRef, Ref. и RefObject. Функция mergeRefs выглядит следующим образом:

export const mergeRefs = (...refs: RefObject<Element | null>[]) => {
  return ( node: Element ) => {
    for (const ref of refs) {
      // @ts-ignore
      ref && !ref.current ? ref.current = node : null;
    }
  }
}

Я прочитал страницу forwardRefsв документации по реакции, что заставило меня поверить, что это путь, по которому я должен пойти в целом, чтобы добавить обработку событий отслеживания ко всем моим компонентам кнопок в приложении. Я также читал различные ответы о переполнении стека при остановке, а затем повторном выпуске собственных событий, таких как submit (которые, например, используются различными частями приложения для захвата Formik, поэтому, почему простая обработка кликов сама по себе не является достаточный). Эти три ответа помогли мне оказаться там, где я сейчас:

Как я могу вызвать React useRef условно в обертке Portal и иметь лучший код?

Как использовать рефы в React с Typescript

Компонент React высшего порядка для отслеживания кликов

На данный момент, однако, я получаю сообщение об ошибке о том, что не могу вызвать dispatchEvent для объекта event, поскольку, по-видимому, типы SyntheticEvent не могут быть переданы dispatchEvent, и я обеспокоен тем, что ошибаюсь на этом пути. здесь.

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

Я искал в документации по реакции, а также гуглил различные руководства и просматривал кодовые базы различных библиотек отслеживания и интеграций реагирования (таких как Segment и Google Analytics), но не смог найти примеры в этом направлении, что заставляет меня беспокоиться о том, что я м на непроторенном и потому неверном пути и имея принципиальное непонимание.

Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Навигация по приложениям React: Исчерпывающее руководство по React Router
Навигация по приложениям React: Исчерпывающее руководство по React Router
React Router стала незаменимой библиотекой для создания одностраничных приложений с навигацией в React. В этой статье блога мы подробно рассмотрим...
Массив зависимостей в React
Массив зависимостей в React
Все о массиве Dependency и его связи с useEffect.
1
0
57
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Не должно быть необходимости обрабатывать внутренний ref для вашего варианта использования, просто перенаправить его.

Просто не забудьте обернуть обработчик события onClick своей собственной реализацией, чтобы сначала вызвать функцию analyticsTrack:

const CustomButton = forwardRef<any, CustomButtonProps>(
  ({ trackingName, children, onClick, ...rest }, ref) => {
    function handleClick(event: any) {
      analyticsTrack("Button Clicked", {
        props: rest,
        ariaLabel: rest["aria-label"],
        trackingName,
        buttonText: typeof children === "string" ? children : ""
      });
      if (onClick) {
        onClick(event);
      }
    }

    return (
      <Button {...rest} onClick = {handleClick} ref = {ref}>
        {children}
      </Button>
    );
  }
);

Демо: https://codesandbox.io/s/nameless-frog-jtdves?file=/src/App.tsx

Это уже работает, моя забота — захватывать и повторять такие события, как submit

Caleb Jay 12.01.2023 07:23

Если да, то по какой причине вы stopPropagation()?

ghybs 12.01.2023 09:40

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

Caleb Jay 12.01.2023 11:30

Если вам нужно управлять внутренним ref и в то же время выставлять его (но потребляющий компонент может его не предоставлять), сделайте это, например, как описано в stackoverflow.com/questions/73724784/…. При этом будьте осторожны при отправке вашего события, чтобы не создавать бесконечный цикл...

ghybs 12.01.2023 11:37

достаточно хорошо для меня, технически этот комментарий является ответом

Caleb Jay 16.01.2023 06:30

Другие вопросы по теме