Как повысить производительность при обнаружении нескольких прослушивателей внешних событий в React Hooks - PullRequest
0 голосов
/ 10 октября 2019

После поиска по теме " Обнаружение снаружи нажмите React Hooks component ", я не могу найти решение для улучшения производительности моего текущего приложения.

  1. Контекст : У меня есть несколько компонентов React:

    • Приложение : корневой компонент, имеет состояние itemSelecting для определения выбора текущего элемента (FirstComponent или SecondComponent, два разных компонента). Он имеет mousedown/mouseup прослушиватель событий для обнаружения внешнего щелчка FirstComponent или SecondComponent
import React, { useState, useEffect } from 'react';
import ReactDOM from 'react-dom';

import './styles.css';

import FirstComponent from './components/FirstComponent';
import SecondComponent from './components/SecondComponent';
import DropDownComponent from './components/DropdownComponent';

function App() {
  const [itemSelecting, setItemSelecting] = useState(0);

  function handleClickOutside(event) {
    // FIXME: how to detect outside click 4 components
    // console.log(event.target);
    if (itemSelecting !== -1) setItemSelecting(0);
    console.log('click first/second component');
  }

  useEffect(() => {
    if (itemSelecting !== -1) {
      document.addEventListener('mousedown', handleClickOutside);
    } else {
      document.removeEventListener('mousedown', handleClickOutside);
    }
  }, []);

  const handleClick = value => {
    if (value === itemSelecting) setItemSelecting(0);
    else setItemSelecting(value);
  };

  return (
    <React.Fragment>
      <div className="App">
        <FirstComponent
          label="01"
          selected={itemSelecting === 1}
          handleClick={() => handleClick(1)}
        />
        <SecondComponent
          label="02"
          selected={itemSelecting === 2}
          handleClick={() => handleClick(2)}
        />
        <FirstComponent
          label="03"
          selected={itemSelecting === 3}
          handleClick={() => handleClick(3)}
        />
        <SecondComponent
          label="04"
          selected={itemSelecting === 4}
          handleClick={() => handleClick(4)}
        />
      </div>
      <hr />
      <DropDownComponent />
    </React.Fragment>
  );
}

const rootElement = document.getElementById('root');
ReactDOM.render(<App />, rootElement);
  • FirstComponent: квадратный компонент имеет selected реквизитов, переданных App. Он может быть выбран ( внутренний щелчок ) / не выбран ( внешний щелчок или щелкнуть еще раз, когда выбрано )
import React from 'react';

import cn from 'classnames';

import styles from '../styles.module.css';

const FirstComponent = ({ label, selected, handleClick }) => {
  const selectedClassName = selected ? styles.selected : '';

  return (
    <div
      className={cn([styles.component, styles.first, selectedClassName])}
      onClick={handleClick}>
      <span>{label}</span>
    </div>
  );
};

export default FirstComponent;
  • SecondComponent : круговой компонент имеет selected пропусков, переданных App. Он может быть выбран ( внутренний щелчок ) / не выбран ( внешний щелчок или щелкнуть снова, когда выбрано )
import React from 'react';

import cn from 'classnames';

import styles from '../styles.module.css';

const SecondComponent = ({ label, selected, handleClick }) => {
  const selectedClassName = selected ? styles.selected : '';

  return (
    <div
      className={cn([styles.component, styles.second, selectedClassName])}
      onClick={handleClick}>
      <span>{label}</span>
    </div>
  );
};

export default SecondComponent;
  • DropdownComponent : раскрывающийся компонент можно развернуть (inside click) / свернуть (outside click). У него есть еще один mousedown/mouseup прослушиватель событий для обнаружения внешнего щелчка DropDownComponent.
import React, { useState, useRef, useEffect } from 'react';
import PropTypes from 'prop-types';
import styles from '../styles.module.css';

const DropDownComponent = () => {
  const nodeRef = useRef(null);
  const listRef = useRef(null);

  const [isOpen, setIsOpen] = useState(false);

  const handleClick = event => {
    if (nodeRef && nodeRef.current.contains(event.target)) {
      // inside click
      if (
        listRef &&
        listRef.current &&
        listRef.current.contains(event.target)
      ) {
        setTimeout(() => {
          setIsOpen(false);
        }, 500);
      }
      return;
    }
    setIsOpen(false);
    console.log('outside click dropdown');
  };

  useEffect(() => {
    document.addEventListener('mousedown', handleClick);
    return () => document.removeEventListener('mousedown', handleClick);
  }, []);

  return (
    <div ref={nodeRef} className={styles.dropdown}>
      <button className={styles.dropbtn} onClick={() => setIsOpen(!isOpen)}>
        Dropdown
      </button>

      {isOpen && (
        <div ref={listRef} className={styles.dropdownContent}>
          <a>Link 1</a>
          <a>Link 2</a>
          <a>Link 3</a>
        </div>
      )}
    </div>
  );
};

DropDownComponent.defaultProps = {
  classNameType: null,
  isOpen: false,
  handleIsOpen: () => {},
  title: null,
};

DropDownComponent.propTypes = {
  classNameType: PropTypes.string,
  isOpen: PropTypes.bool,
  handleIsOpen: PropTypes.func,
  title: PropTypes.string,
};

export default DropDownComponent;
Проблема : В то же время у него есть внешний прослушиватель событий щелчка , избыточный . Пример: при нажатии на один из FirstComponent / SecondComponent появляется:
outside click dropdown 
click first/second component
Цель : как я могу обнаружить по одному разу внешний прослушиватель событий щелчка на нескольких компонентах? Я хочу оптимизировать: при нажатии на один из FirstComponent / SecondComponent вызывается только внешний щелчок DropdownComponent.

Примечание : На самом деле, я хотел быреализовать ref (useRef) внутри каждого компонента (FirstComponent / SecondComponent), чтобы разрешить его, но я не знаю, как (FIXMEв App.js)

Исходный код демо включен CodeSandbox .

Заранее спасибо.

1 Ответ

0 голосов
/ 10 октября 2019

Я думаю, что ваша проблема в том, что выбранный компонент сначала отменяется при нажатии на них. Это происходит потому, что событие onClick срабатывает, когда кнопка мыши отпущена, а внешний щелчок срабатывает в момент нажатия кнопки мыши. Это можно исправить, заменив mousedown на mouseup в addEventListener для внешнего щелчка.

Другая потенциальная проблема может заключаться в том, что вы не удаляете слушателя из окна. Вы должны очистить всю вашу подписку в функции возврата хука useEffect. Для этого лучше переместить объявление обработчика событий внутри useEffect. А поскольку внешний обработчик события click использует значение itemSelecting, вы должны добавить его как зависимость к useEffect.

useEffect(() => {
  const handleClickOutside = (event) => {
    if (itemSelecting !== -1) setItemSelecting(0);
    console.log('click first/second component');
  }
  document.addEventListener('mouseup', handleClickOutside);
  return () => document.removeEventListener('mouseup', handleClickOutside);
}, [itemSelecting]);

Это необязательно, но я бы удалил зависимость itemSelecting из обработчика handleClickOutside. Так что useEffect вызывается только один раз. Таким образом, это может быть проще.

useEffect(() => {
  const handleClickOutside = (event) => {
    setItemSelecting(0);
    console.log('click first/second component');
  }
  document.addEventListener('mouseup', handleClickOutside);
  return () => document.removeEventListener('mouseup', handleClickOutside);
}, []);

Надеюсь, это поможет. https://codesandbox.io/s/detect-multiple-outside-click-on-react-components-1hjg0

...