Javascript 为什么不是';我的事件侦听器回调是否使用正确的状态?
我有一个组件使包装的元素可以拖动。当我开始拖动时,我会将事件侦听器添加到窗口中,以便进行拖放操作Javascript 为什么不是';我的事件侦听器回调是否使用正确的状态?,javascript,reactjs,Javascript,Reactjs,我有一个组件使包装的元素可以拖动。当我开始拖动时,我会将事件侦听器添加到窗口中,以便进行拖放操作 function start_drag({ x, y }) { window.addEventListener('mouseup', trigger_drop); window.addEventListener('mousemove', drag_move); dispatch({ type: DispatchActions.START, x:
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);
}
}
但是,这些回调使用自己版本的state和dispatch。在尝试了一些事情之后,我还没能解决这个问题,此外,我对“这个”在这里的运作方式感到困惑
我在React中工作,只使用带有React钩子的功能组件来表示状态等等。
从许多其他stackoverflow问题中,答案是使用绑定/箭头函数。正如您所看到的,我将回调声明为箭头函数(不起作用),但这导致了一些奇怪的事情;当我尝试绑定时,我发现在我的功能组件中,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>
);
}
import React,{useContext,useffect,useReducer}来自“React”;
从“client/contexts/DragContext”导入{DragContext};
从“./dragtarget.module.css”导入dragtarget;
const DispatchActions={
移动:“移动”,
开始:“开始”,
停止:“停止”
}
功能减速机(状态、动作){
开关(动作类型){
case DispatchActions.MOVE:
返回{…state,offset_x:action.x-(state.start_x+state.offset_x),offset_y:action.y-(state.start_y+state.offset_y)};
case DispatchActions.START:
返回{…状态,拖动:true,start_x:action.x,start_y:action.y,offset_x:0,offset_y:0};
case DispatchActions.STOP:
返回{…状态,拖动:false};
违约:
返回状态;
}
}
导出默认功能DragTarget(道具){
const{drag,deliver}=useContext(DragContext);
const[state,dispatch]=useReducer(reducer,{
拖动:false,
开始x:0,开始y:0,
偏移量x:0,偏移量y:0
});
useffect(()=>{
return()=>{
结束拖动();
};
}, []);
函数开始拖动({x,y}){
window.addEventListener('mouseup',trigger_drop);
window.addEventListener('mousemove',drag\u move);
分派({type:DispatchActions.START,x:x,y:y});
}
函数end_drag(){
window.removeEventListener('mouseup',trigger_drop);
removeEventListener('mousemove',拖放);
分派({type:DispatchActions.STOP});
}
常量触发器拖动=(e)=>{
e、 停止传播();
e、 预防默认值();
if(阻力(道具有效载荷)){
开始拖动({x:e.x,y:e.y});
如果(道具开始){
props.onDragStart();
}
}
}
常量拖动\移动=(e)=>{
//如果(!state.drawing){return;}
分派({type:DispatchActions.MOVE,x:e.x,y:e.y});
如果(道具与碎片){
(e)支柱;
}
}
常数触发器_下降=(e)=>{
//如果(!state.drawing){return;}
结束拖动();
if(deliver()){
如果(道具onDrop){
道具.昂德罗普(e);
}
}
}
返回(
{props.children}
);
}
预期:在window.mouseup上,我希望回调触发器能够访问正确的状态。拖动和调度。在window.mousemove上拖动和移动也一样
当前:在window.mouse上,将回调触发器拖放到state.dragging的副本中,返回false
(而不是引用具有true
)的正确副本,并且drag\u move将分派到其中包含未定义元素的状态(state=={拖动:true,start\u x:undefined,start\u y:undefined,offset\u x:NaN,offset\u y:NaN}
)
我希望我能解释清楚,如果没有请告诉我。提前谢谢你的帮助!我想你可以试试e=>trigger\u drop(e,道具,调度)
获取正确的值和分派函数。一种更简单的方法是,不再分派异步操作,而是使用可重用组件,将其自身状态作为单个对象处理,并进行同步setState
回调更新
例如,您可以使用两个事件侦听器和一个事件回调来简化逻辑:一个用于mouseup
(鼠标单击)的事件侦听器用于保存元素,另一个用于mousemove
(当按住鼠标单击并移动鼠标时)要转换元素,最后可以使用元素的onMouseDown
(鼠标单击释放)事件回调在其当前位置释放自身
工作示例(此示例使用样式化组件
来获得更清晰的代码,但您不需要):
组件/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;
`}
`;
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;
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"));
组件/Draggable/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;
`}
`;
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;
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"));
import-React{
useState,
useRef,
使用效果,
使用回调,
使用布局效果
}从“反应”;
从“道具类型”导入道具类型;
从“./DragContainer”导入DragContainer;
常量拖动=({子对象,位置})=>{
const dragRef=useRef(null);
常量[状态,设置状态]=使用状态({
IsDraging:错,
translateX:position.x,
translateY:position.y,
高度:0,,
宽度:0
});
//鼠标移动
const handleMouseMove=useCallback(
({clientX,clientY})=>{
if(state.isDraging){
设置状态(prevState=>({
…国家,
translateX:clientX,
translateY:clientY
}));