Как провести рефакторинг класса 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