[英]Why isn't my event listener callback using the correct state?
我有一个使包裹元素可拖动的组件。 开始拖动时,我将事件侦听器添加到窗口中以进行拖动移动和放置。
function start_drag({ x, y }) {
window.addEventListener('mouseup', trigger_drop);
window.addEventListener('mousemove', drag_move);
dispatch({ type: DispatchActions.START, x: x, y: y });
}
通过这些回调:
const trigger_drop = (e) => {
//if (!dragging) { return; }
end_drag();
if (deliver()) {
if (props.onDrop) {
props.onDrop(e);
}
}
}
const drag_move = (e) => {
//if (!state.dragging) { return; }
dispatch({ type: DispatchActions.MOVE, x: e.x, y: e.y });
if (props.onDragMove) {
props.onDragMove(e);
}
}
但是,这些回调使用自己的状态和调度版本。 在尝试了几件事之后,我无法解决此问题,此外,我对“ this”在此处的工作方式感到困惑。
我在React中工作,仅使用带有React Hooks的功能组件来获取状态等等。 对于许多其他stackoverflow问题,答案是使用绑定/箭头功能。 如您所见,我将回调声明为箭头函数(不起作用),但是这使我感到有些奇怪。 当我尝试绑定时,我发现this === undefined
在我的功能组件中this === undefined
。 这可能是相关的。 我对此的搜索仅给出了答案,要求将其绑定到React.Component类的构造函数中,在这里不起作用。
这是该模块的完整代码:
import React, { useContext, useEffect, useReducer } from 'react';
import { DragContext } from 'client/contexts/DragContext';
import dragtarget from './DragTarget.module.css';
const DispatchActions = {
MOVE: 'move',
START: 'start',
STOP: 'stop'
}
function reducer(state, action) {
switch(action.type) {
case DispatchActions.MOVE:
return { ...state, offset_x: action.x - (state.start_x + state.offset_x), offset_y: action.y - (state.start_y + state.offset_y) };
case DispatchActions.START:
return { ...state, dragging: true, start_x: action.x, start_y: action.y, offset_x: 0, offset_y: 0 };
case DispatchActions.STOP:
return { ...state, dragging: false };
default:
return state;
}
}
export default function DragTarget(props) {
const { drag, deliver } = useContext(DragContext);
const [state, dispatch] = useReducer(reducer, {
dragging: false,
start_x: 0, start_y: 0,
offset_x: 0, offset_y: 0
});
useEffect(() => {
return () => {
end_drag();
};
}, []);
function start_drag({ x, y }) {
window.addEventListener('mouseup', trigger_drop);
window.addEventListener('mousemove', drag_move);
dispatch({ type: DispatchActions.START, x: x, y: y });
}
function end_drag() {
window.removeEventListener('mouseup', trigger_drop);
window.removeEventListener('mousemove', drag_move);
dispatch({ type: DispatchActions.STOP });
}
const trigger_drag = (e) => {
e.stopPropagation();
e.preventDefault();
if (drag(props.payload)) {
start_drag({ x: e.x, y: e.y });
if (props.onDragStart) {
props.onDragStart();
}
}
}
const drag_move = (e) => {
//if (!state.dragging) { return; }
dispatch({ type: DispatchActions.MOVE, x: e.x, y: e.y });
if (props.onDragMove) {
props.onDragMove(e);
}
}
const trigger_drop = (e) => {
//if (!state.dragging) { return; }
end_drag();
if (deliver()) {
if (props.onDrop) {
props.onDrop(e);
}
}
}
return (
<div className={`${props.className} ${state.dragging ? dragtarget.dragging : null}`} style={{ transform: `translate(${state.offset_x}px, ${state.offset_y}px)` }} onMouseDown={trigger_drag}>
{props.children}
</div>
);
}
预期:在window.mouseup上,我希望回调trigger_drop访问正确的state.dragging
和dispatch
。 与window.mousemove上的drag_move相同。
当前:在window.mouseup上,回调trigger_drop的state.dragging返回false
(而不是引用正确的具有true
),并且drag_move调度到其中包含未定义元素的状态(state === {dragging: true, start_x: undefined, start_y: undefined, offset_x: NaN, offset_y: NaN}
)。
我希望我能清楚地解释这一点,否则请告诉我。 预先感谢您的任何帮助!
一种更简单的方法是摆脱分派异步操作,而使用可重用的组件,该组件将具有自己的状态作为单个对象处理,并带有同步setState
回调更新。
例如,您可以通过两个事件侦听器和一个事件回调来简化逻辑:一个事件侦听器用于mouseup
(单击鼠标)来保存元素,另一个事件侦听器用于mousemove
(按住鼠标单击并移动鼠标时)进行翻译最后,您可以使用元素的onMouseDown
(鼠标单击释放)事件回调在其当前位置释放自身。
工作示例 (此示例将styled-components
用于更干净的代码,但您无需这样做):
组件/DragContainer/index.js
import styled from "styled-components";
export default styled.div.attrs(({ height, width, x, y }) => ({
style: {
transform: `translate(${x - width / 2}px, ${y - height / 2}px)`
}
}))`
cursor: grab;
position: absolute;
padding: 10px;
border-radius: 4px;
background-color: red;
${({ isDragging }) =>
isDragging &&
`
opacity: 0.5;
cursor: grabbing;
z-index: 999999;
`}
`;
组件/可拖动/index.js
import React, {
useState,
useRef,
useEffect,
useCallback,
useLayoutEffect
} from "react";
import PropTypes from "prop-types";
import DragContainer from "../DragContainer";
const Draggable = ({ children, position }) => {
const dragRef = useRef(null);
const [state, setState] = useState({
isDragging: false,
translateX: position.x,
translateY: position.y,
height: 0,
width: 0
});
// mouse move
const handleMouseMove = useCallback(
({ clientX, clientY }) => {
if (state.isDragging) {
setState(prevState => ({
...prevState,
translateX: clientX,
translateY: clientY
}));
}
},
[state.isDragging]
);
// mouse left click release
const handleMouseUp = useCallback(() => {
if (state.isDragging) {
setState(prevState => ({
...prevState,
isDragging: false
}));
}
}, [state.isDragging]);
// mouse left click hold
const handleMouseDown = useCallback(() => {
setState(prevState => ({
...prevState,
isDragging: true
}));
}, []);
// before painting, get element height and width
// and zero out its position (this is
// necessary to force the cursor to point at the
// center of the element when dragging it)
useLayoutEffect(() => {
if (state.height < 1 && state.width < 1) {
const { offsetHeight, offsetWidth } = dragRef.current;
setState(prevState => ({
...prevState,
translateX: position.x + offsetWidth / 2,
translateY: position.y + offsetHeight / 2,
height: offsetHeight,
width: offsetWidth
}));
}
}, [position, state, setState, dragRef]);
useEffect(() => {
window.addEventListener("mousemove", handleMouseMove);
window.addEventListener("mouseup", handleMouseUp);
return () => {
window.removeEventListener("mousemove", handleMouseMove);
window.removeEventListener("mouseup", handleMouseUp);
};
}, [handleMouseMove, handleMouseUp]);
return (
<DragContainer
ref={dragRef}
isDragging={state.isDragging}
onMouseDown={handleMouseDown}
x={state.translateX}
y={state.translateY}
height={state.height}
width={state.width}
>
{children}
</DragContainer>
);
};
Draggable.propTypes = {
children: PropTypes.node.isRequired,
position: PropTypes.shape({
x: PropTypes.number,
y: PropTypes.number
})
};
Draggable.defaultProps = {
position: {
x: 10,
y: 10
}
};
export default Draggable;
index.js
import React, { Fragment } from "react";
import { render } from "react-dom";
import { Draggable, Title } from "./components";
const App = () => (
<Fragment>
<Draggable position={{ x: 20, y: 20 }}>
<Title>Hello</Title>
</Draggable>
<Draggable position={{ x: 140, y: 20 }}>
<Title>Goodbye</Title>
</Draggable>
</Fragment>
);
render(<App />, document.getElementById("root"));
我认为您可以尝试e => trigger_drop(e, props, dispatch)
以获取正确的值和调度功能。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.