简体   繁体   English

为什么我的事件监听器回调没有使用正确的状态?

[英]Why isn't my event listener callback using the correct state?

I have a component to make wrapped elements draggable. 我有一个使包裹元素可拖动的组件。 When I start dragging, I add event listeners to window for drag movement and drop. 开始拖动时,我将事件侦听器添加到窗口中以进行拖动移动和放置。

    function start_drag({ x, y }) {
        window.addEventListener('mouseup', trigger_drop);
        window.addEventListener('mousemove', drag_move);
        dispatch({ type: DispatchActions.START, x: x, y: y });
    }

With these callbacks: 通过这些回调:

    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);
        }
    }

However, these callbacks are using their own versions of state and dispatch. 但是,这些回调使用自己的状态和调度版本。 After trying a few things I haven't been able to fix this, and in addition I'm confused about the way 'this' is functioning here. 在尝试了几件事之后,我无法解决此问题,此外,我对“ this”在此处的工作方式感到困惑。

I am working in React, using only functional components with React Hooks for state and so on. 我在React中工作,仅使用带有React Hooks的功能组件来获取状态等等。 From many other stackoverflow questions the answers have been to use binding/arrow functions. 对于许多其他stackoverflow问题,答案是使用绑定/箭头功能。 As you can see I'm declaring my callbacks as arrow functions (which isn't working) but this led me to something strange; 如您所见,我将回调声明为箭头函数(不起作用),但是这使我感到有些奇怪。 when I tried binding I found out that this === undefined in my functional component. 当我尝试绑定时,我发现this === undefined在我的功能组件中this === undefined This is probably related. 这可能是相关的。 My searches for this only came up with answers saying to bind this in the constructor for a React.Component class, which doesn't work here. 我对此的搜索仅给出了答案,要求将其绑定到React.Component类的构造函数中,在这里不起作用。

Here is the full code for the module: 这是该模块的完整代码:

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>
    );
}

Expected: On window.mouseup I want the callback trigger_drop to access the correct state.dragging and dispatch . 预期:在window.mouseup上,我希望回调trigger_drop访问正确的state.draggingdispatch Same for drag_move on window.mousemove. 与window.mousemove上的drag_move相同。

Current: On window.mouseup the callback trigger_drop's copy of state.dragging returns false (instead of referencing the correct one, which has true ), and drag_move is dispatching to a state with undefined elements in it (state === {dragging: true, start_x: undefined, start_y: undefined, offset_x: NaN, offset_y: NaN} ). 当前:在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} )。

I hope I explained this clearly, if not please let me know. 我希望我能清楚地解释这一点,否则请告诉我。 Thank you in advance for any help! 预先感谢您的任何帮助!

An easier approach would be to move away from dispatching asynchronous actions and instead utilize a reusable component that handles its own state as a single object with synchronous setState callback updates. 一种更简单的方法是摆脱分派异步操作,而使用可重用的组件,该组件将具有自己的状态作为单个对象处理,并带有同步setState回调更新。

For example, you can simplify your logic with two event listeners and one event callback: one event listener for mouseup (a mouse click) to hold the element, another event listener for mousemove (when holding a mouse click and moving the mouse) to translate the element, and lastly you can use the element's onMouseDown (mouse click release) event callback to release itself at its current position. 例如,您可以通过两个事件侦听器和一个事件回调来简化逻辑:一个事件侦听器用于mouseup (单击鼠标)来保存元素,另一个事件侦听器用于mousemove (按住鼠标单击并移动鼠标时)进行翻译最后,您可以使用元素的onMouseDown (鼠标单击释放)事件回调在其当前位置释放自身。

Working example (this example uses styled-components for cleaner code, but you don't need to): 工作示例 (此示例将styled-components用于更干净的代码,但您无需这样做):

编辑拖放内容示例


components/DragContainer/index.js 组件/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;
  `}
`;

components/Draggable/index.js 组件/可拖动/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 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM