Как провести рефакторинг класса React mousemove до функционального компонента?

У меня есть кнопка, которая закрывает навигацию. Эта кнопка следует за мышью. Все работает, но у меня есть предупреждение о депригации, от которого я хочу избавиться, но не знаю, как именно. (Я знаю только, что useEffect играет определенную роль:

Вот класс:

import React from "react"

class NavigationCloseMouseButton extends React.Component {
  static defaultProps = {
    visible: true,
    offsetX: 0,
    offsetY: 0,
  }

  state = {
    xPosition: 0,
    yPosition: 0,
    mouseMoved: false,
    listenerActive: false,
  }

  componentDidMount() {
    this.addListener()
  }

  componentDidUpdate() {
    this.updateListener()
  }

  componentWillUnmount() {
    this.removeListener()
  }

  getTooltipPosition = ({ clientX: xPosition, clientY: yPosition }) => {
    this.setState({
      xPosition,
      yPosition,
      mouseMoved: true,
    })
  }

  addListener = () => {
    window.addEventListener("mousemove", this.getTooltipPosition)
    this.setState({ listenerActive: true })
  }

  removeListener = () => {
    window.removeEventListener("mousemove", this.getTooltipPosition)
    this.setState({ listenerActive: false })
  }

  updateListener = () => {
    if (!this.state.listenerActive && this.props.visible) {
      this.addListener()
    }

    if (this.state.listenerActive && !this.props.visible) {
      this.removeListener()
    }
  }

  render() {
    return (
      <div
        onClick={this.props.toggleNavigation}
        className="tooltip color-bg"
        style={{
          display:
            this.props.visible && this.state.mouseMoved ? "block" : "none",
          opacity: this.props.visible && this.state.mouseMoved ? "1" : "0",
          top: this.state.yPosition + this.props.offsetY,
          left: this.state.xPosition + this.props.offsetX,
        }}
      >
        Close Menu
      </div>
    )
  }
}

export default NavigationCloseMouseButton

И это то, что я пока что, но результаты с ошибками:ReferenceError: getTooltipPosition is not defined


import React, { useState, useEffect } from "react"

const NavigationCloseMouseButton = () => {
  const defaults = {
    visible: true,
    offsetX: 0,
    offsetY: 0,
  }

  const defaultState = {
    xPosition: 0,
    yPosition: 0,
    mouseMoved: false,
    listenerActive: false,
  }

  const [defaultProps, setDefaultProps] = useState(defaults)
  const [state, setState] = useState(defaultState)

  useEffect(() => {
    // Update the document title using the browser API
    addListener()
  }, [])

  getTooltipPosition = ({ clientX: xPosition, clientY: yPosition }) => {
    setState({
      xPosition,
      yPosition,
      mouseMoved: true,
    })
  }

  addListener = () => {
    window.addEventListener("mousemove", getTooltipPosition)
    setState({ listenerActive: true })
  }

  removeListener = () => {
    window.removeEventListener("mousemove", getTooltipPosition)
    setState({ listenerActive: false })
  }

  updateListener = () => {
    if (!state.listenerActive && props.visible) {
      addListener()
    }

    if (state.listenerActive && !props.visible) {
      removeListener()
    }
  }

  return (
    <div
      onClick={props.toggleNavigation}
      className="tooltip color-bg"
      style={{
        display: props.visible && state.mouseMoved ? "block" : "none",
        opacity: props.visible && state.mouseMoved ? "1" : "0",
        top: state.yPosition + props.offsetY,
        left: state.xPosition + props.offsetX,
      }}
    >
      Close Menu
    </div>
  )
}

export default NavigationCloseMouseButton

1 ответ

Установка значений по умолчанию

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

      const NavigationCloseMouseButton = ({ visible = true, offsetX = 0, offsetY = 0, toggleNavigation }) => {

Обновление слушателя

Я уверен, что есть много отличных ответов по этому поводу, поэтому я не буду вдаваться в подробности.

Вы хотите обрабатывать добавление и удаление слушателя из вашего. Вы должны использовать useEffectфункция очистки для окончательного удаления. Мы не хотим добавлять и удалять одного и того же слушателя, поэтому мы можем запоминать его с помощью useCallback.

Я не уверен, что вы пытаетесь делать с listenerActive. Это может быть опора, но она также кажется немного избыточной с visible. Я не знаю, что нам это вообще нужно.

Расчет смещения

Я тоже не знаю, есть ли смысл сдавать offsetX и offsetYкак реквизит. Нам нужно, чтобы мышь находилась над всплывающей подсказкой, чтобы на нее можно было нажимать. Мы можем измерить подсказку div внутри этого компонента и поступайте с ним таким же образом.

      // ref to DOM node for measuring
const divRef = useRef<HTMLDivElement>(null);

// can caluculate offset instead of passing in props
const offsetX = -.5 * (divRef.current?.offsetWidth || 0);
const offsetY = -.5 * (divRef.current?.offsetHeight || 0);

Анимация

Установка свойства стиля display в качестве "block" или же "none"затрудняет выполнение какого-либо перехода CSS. Вместо этого я рекомендую вам управлять переключением стилей, изменяя файл. Вы все еще можете установить display: block и display: none в этих классах, но я предпочитаю использовать transform: scale(0); вместо.

Код

      const NavigationCloseMouseButton = ({
  visible = true,
  toggleNavigation
}) => {

  // state of the movement
  const [state, setState] = useState({
    xPosition: 0,
    yPosition: 0,
    mouseMoved: false
  });

  // memoized event listener
  const getTooltipPosition = useCallback(
    // plain event, not a React synthetic event
    ({ clientX: xPosition, clientY: yPosition }) => {
      setState({
        xPosition,
        yPosition,
        mouseMoved: true
      });
    },
    []
  ); // never re-creates

  useEffect(() => {
    // don't need to listen when it's not visible
    if (visible) {
      window.addEventListener("mousemove", getTooltipPosition);
    } else {
      window.removeEventListener("mousemove", getTooltipPosition);
    }

    // clean-up function to remove on unmount
    return () => {
      window.removeEventListener("mousemove", getTooltipPosition);
    };
  }, [visible, getTooltipPosition]); // re-run the effect if prop `visible` changes

  // ref to DOM node for measuring
  const divRef = useRef(null);

  // can caluculate offset instead of passing in props
  const offsetX = -.5 * (divRef.current?.offsetWidth || 0);
  const offsetY = -.5 * (divRef.current?.offsetHeight || 0);

  // don't show until after mouse is moved
  const isVisible = visible && state.mouseMoved;

  return (
    <div
      ref={divRef}
      onClick={toggleNavigation}
      // control most styling through className
      className={`tooltip ${isVisible ? "tooltip-visible" : "tooltip-hidden"}`}
      style={{
        // need absolute position to use top and left
        position: "absolute",
        top: state.yPosition + offsetY,
        left: state.xPosition + offsetX
      }}
    >
      Close Menu
    </div>
  );
};

Другое использование

Мы легко можем это сделать NavigationCloseMouseButton в более гибкий MovingTooltip удалив некоторые жестко запрограммированные особенности.

  • Получить содержимое из props.children вместо того, чтобы всегда использовать "Закрыть меню"
  • Принять className как опора
  • Измените название toggleNavigation к onClick

Демонстрация тестовой среды кода

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