我正在尝试使用反应钩创建这个旋转的div
示例的副本。 https://codesandbox.io/s/XDjY28XoV
到目前为止,这是我的代码
import React, { useState, useEffect, useCallback } from 'react';
const App = () => {
const [box, setBox] = useState(null);
const [isActive, setIsActive] = useState(false);
const [angle, setAngle] = useState(0);
const [startAngle, setStartAngle] = useState(0);
const [currentAngle, setCurrentAngle] = useState(0);
const [boxCenterPoint, setBoxCenterPoint] = useState({});
const setBoxCallback = useCallback(node => {
if (node !== null) {
setBox(node)
}
}, [])
// to avoid unwanted behaviour, deselect all text
const deselectAll = () => {
if (document.selection) {
document.selection.empty();
} else if (window.getSelection) {
window.getSelection().removeAllRanges();
}
}
// method to get the positionof the pointer event relative to the center of the box
const getPositionFromCenter = e => {
const fromBoxCenter = {
x: e.clientX - boxCenterPoint.x,
y: -(e.clientY - boxCenterPoint.y)
};
return fromBoxCenter;
}
const mouseDownHandler = e => {
e.stopPropagation();
const fromBoxCenter = getPositionFromCenter(e);
const newStartAngle =
90 - Math.atan2(fromBoxCenter.y, fromBoxCenter.x) * (180 / Math.PI);
setStartAngle(newStartAngle);
setIsActive(true);
}
const mouseUpHandler = e => {
deselectAll();
e.stopPropagation();
if (isActive) {
const newCurrentAngle = currentAngle + (angle - startAngle);
setIsActive(false);
setCurrentAngle(newCurrentAngle);
}
}
const mouseMoveHandler = e => {
if (isActive) {
const fromBoxCenter = getPositionFromCenter(e);
const newAngle =
90 - Math.atan2(fromBoxCenter.y, fromBoxCenter.x) * (180 / Math.PI);
box.style.transform =
"rotate(" +
(currentAngle + (newAngle - (startAngle ? startAngle : 0))) +
"deg)";
setAngle(newAngle)
}
}
useEffect(() => {
if (box) {
const boxPosition = box.getBoundingClientRect();
// get the current center point
const boxCenterX = boxPosition.left + boxPosition.width / 2;
const boxCenterY = boxPosition.top + boxPosition.height / 2;
// update the state
setBoxCenterPoint({ x: boxCenterX, y: boxCenterY });
}
// in case the event ends outside the box
window.onmouseup = mouseUpHandler;
window.onmousemove = mouseMoveHandler;
}, [ box ])
return (
<div className="box-container">
<div
className="box"
onMouseDown={mouseDownHandler}
onMouseUp={mouseUpHandler}
ref={setBoxCallback}
>
Rotate
</div>
</div>
);
}
export default App;
目前,即使状态实际为true,也会调用具有isActive = false
状态的mouseMoveHandler。如何使用正确的状态启动此事件处理程序?
此外,控制台正在记录警告:
React Hook useEffect has missing dependencies: 'mouseMoveHandler' and 'mouseUpHandler'. Either include them or remove the dependency array react-hooks/exhaustive-deps
为什么我必须在useEffect依赖关系数组中包含组件方法?我从来没有必要使用React Hooks为其他更简单的组件执行此操作。
谢谢
为什么
isActive
假?
const mouseMoveHandler = e => {
if(isActive) {
// ...
}
};
(为方便起见,我只是谈论mouseMoveHandler
,但这里的一切也适用于mouseUpHandler
)
当上面的代码运行时,会创建一个函数实例,它通过isActive
引入function closure变量。该变量是一个常量,因此如果在定义函数时isActive
为false,那么只要函数实例存在,它就总是为false
。
useEffect
也接受一个函数,该函数对你的moveMouseHandler
函数实例有一个常量引用 - 所以只要存在useEffect回调,它就会引用moveMouseHandler
的副本,其中isActive
为false。
当isActive
改变时,组件会重新渲染,并且将创建一个新的moveMouseHandler
实例,其中isActive
是true
。但是,如果依赖关系发生了变化,useEffect
只重新运行它的函数 - 在这种情况下,依赖关系([box]
)没有改变,所以useEffect
没有重新运行,并且moveMouseHandler
为false的isActive
的版本仍然附加到窗口,无论目前的状态如何。
这就是为什么“详尽的deps”钩子警告你关于useEffect
的原因 - 它的一些依赖关系可以改变,而不会导致钩子重新运行并更新这些依赖关系。
由于钩子间接依赖于isActive
,你可以通过将isActive
添加到deps
的useEffect
数组来解决这个问题:
// Works, but not the best solution
useEffect(() => {
//...
}, [box, isActive])
但是,这不是很干净:如果你改变mouseMoveHandler
以便它依赖于更多状态,你将会遇到同样的错误,除非你记得将它添加到deps
数组中。 (也是linter不喜欢这个)
useEffect
函数间接依赖于isActive
,因为它直接依赖于mouseMoveHandler
;所以你可以将它添加到依赖项:
useEffect(() => {
//...
}, [box, mouseMoveHandler])
通过此更改,useEffect将重新运行mouseMoveHandler
的新版本,这意味着它将尊重isActive
。然而,它会经常运行 - 每次mouseMoveHandler
成为一个新的函数实例时都会运行...这是每个渲染,因为每个渲染都会创建一个新函数。
我们并不需要在每次渲染时都创建一个新函数,只有当isActive
发生了变化时:React为该用例提供了useCallback
钩子。您可以将mouseMoveHandler
定义为
const mouseMoveHandler = useCallback(e => {
if(isActive) {
// ...
}
}, [isActive])
现在只有当isActive
改变时才会创建一个新的函数实例,然后触发useEffect
在适当的时刻运行,你可以更改mouseMoveHandler
的定义(例如添加更多状态)而不会破坏你的useEffect
钩子。
这可能仍然会引入你的useEffect
钩子的问题:每次isActive
改变时它都会重新运行,这意味着每次isActive
改变时它都会设置盒子中心点,这可能是不需要的。您应该将效果分成两个单独的效果以避免此问题:
useEffect(() => {
// update box center
}, [box])
useEffect(() => {
// expose window methods
}, [mouseMoveHandler, mouseUpHandler]);
最终,您的代码应如下所示:
const mouseMoveHandler = useCallback(e => {
/* ... */
}, [isActive]);
const mouseUpHandler = useCallback(e => {
/* ... */
}, [isActive]);
useEffect(() => {
/* update box center */
}, [box]);
useEffect(() => {
/* expose callback methods */
}, [mouseUpHandler, mouseMoveHandler])
React作者之一丹·阿布拉莫夫(Dan Abramov)在他的Complete Guide to useEffect博文中详细介绍了一些细节。
React Hooks useState+useEffect+event gives stale state。好像你有类似的问题。基本问题是“它从定义它的闭包中获得它的价值”
尝试解决方案2“使用参考”。在你的场景中
添加以下useRef和useEffect
let refIsActive = useRef(isActive);
useEffect(() => {
refIsActive.current = isActive;
});
然后在mouseMoveHandler里面,使用那个ref
const mouseMoveHandler = (e) => {
console.log('isActive',refIsActive.current);
if (refIsActive.current) {