react长按事件

IT技术 reactjs create-react-app dom-events react-web
2021-03-27 05:03:15

有没有办法react-web 应用程序中添加长按事件

我有地址列表。长按任何地址时,我想触发事件以删除该地址,然后是确认框。

6个回答

我创建了一个带有钩子代码沙盒来处理长按和点击。基本上,在鼠标按下、触摸开始事件时,会使用setTimeout. 当提供的时间过去时,它会触发长按。在鼠标抬起、鼠标离开、触摸结束等时,计时器被清除。

useLongPress.js

import { useCallback, useRef, useState } from "react";

const useLongPress = (
    onLongPress,
    onClick,
    { shouldPreventDefault = true, delay = 300 } = {}
    ) => {
    const [longPressTriggered, setLongPressTriggered] = useState(false);
    const timeout = useRef();
    const target = useRef();

    const start = useCallback(
        event => {
            if (shouldPreventDefault && event.target) {
                    event.target.addEventListener("touchend", preventDefault, {
                    passive: false
                });
                target.current = event.target;
            }
            timeout.current = setTimeout(() => {
                onLongPress(event);
                setLongPressTriggered(true);
            }, delay);
        },
        [onLongPress, delay, shouldPreventDefault]
    );

    const clear = useCallback(
        (event, shouldTriggerClick = true) => {
            timeout.current && clearTimeout(timeout.current);
            shouldTriggerClick && !longPressTriggered && onClick();
            setLongPressTriggered(false);
            if (shouldPreventDefault && target.current) {
                target.current.removeEventListener("touchend", preventDefault);
            }
        },
        [shouldPreventDefault, onClick, longPressTriggered]
    );

    return {
        onMouseDown: e => start(e),
        onTouchStart: e => start(e),
        onMouseUp: e => clear(e),
        onMouseLeave: e => clear(e, false),
        onTouchEnd: e => clear(e)
    };
};

const isTouchEvent = event => {
return "touches" in event;
};

const preventDefault = event => {
if (!isTouchEvent(event)) return;

if (event.touches.length < 2 && event.preventDefault) {
    event.preventDefault();
}
};

export default useLongPress;

要使用钩子, App.js

import useLongPress from "./useLongPress";

export default function App() {

    const onLongPress = () => {
        console.log('longpress is triggered');
    };

    const onClick = () => {
        console.log('click is triggered')
    }

    const defaultOptions = {
        shouldPreventDefault: true,
        delay: 500,
    };
    const longPressEvent = useLongPress(onLongPress, onClick, defaultOptions);

    return (
        <div className="App">
            <button {...longPressEvent}>use  Loooong  Press</button>
        </div>
    );
}

类组件的旧答案:

您可以使用 MouseDown、MouseUp、TouchStart、TouchEnd 事件来控制可以充当长按事件的计时器。查看下面的代码

class App extends Component {
  constructor() {
    super()
    this.handleButtonPress = this.handleButtonPress.bind(this)
    this.handleButtonRelease = this.handleButtonRelease.bind(this)
  }
  handleButtonPress () {
    this.buttonPressTimer = setTimeout(() => alert('long press activated'), 1500);
  }
  
  handleButtonRelease () {
    clearTimeout(this.buttonPressTimer);
  }

  render() {
    return (
      <div 
          onTouchStart={this.handleButtonPress} 
          onTouchEnd={this.handleButtonRelease} 
          onMouseDown={this.handleButtonPress} 
          onMouseUp={this.handleButtonRelease} 
          onMouseLeave={this.handleButtonRelease}>
        Button
      </div>
    );
  }
}
您还需要添加 onMouseLeave={this.handleButtonRelease}
2021-05-25 05:03:15
这很棒!目前编写的 FWIW onClick 没有传递事件对象。你可以说事件对象是一个“mouseup”事件,所以 onclick 不应该得到它,但在我的情况下,我需要它,所以我调整了它以传递事件。👍
2021-05-26 05:03:15
@AnthonyKong,我已经用钩子更新了答案,包括处理点击的解决方案。
2021-06-02 05:03:15
在带有事件参数 ( const onClick = (e) => { ...)的回调中e仅在onLongPressundefined定义onClick如何从共享相同的多个按钮传递参数{...longPressEvent}
2021-06-06 05:03:15
如果我还想同时处理普通的鼠标点击怎么办?(如果答案会很长,请告诉我。我会提出一个新问题)
2021-06-09 05:03:15

使用 react 16.8 中的钩子,您可以使用函数和钩子重写类。

import { useState, useEffect } from 'react';

export default function useLongPress(callback = () => {}, ms = 300) {
  const [startLongPress, setStartLongPress] = useState(false);

  useEffect(() => {
    let timerId;
    if (startLongPress) {
      timerId = setTimeout(callback, ms);
    } else {
      clearTimeout(timerId);
    }

    return () => {
      clearTimeout(timerId);
    };
  }, [callback, ms, startLongPress]);

  return {
    onMouseDown: () => setStartLongPress(true),
    onMouseUp: () => setStartLongPress(false),
    onMouseLeave: () => setStartLongPress(false),
    onTouchStart: () => setStartLongPress(true),
    onTouchEnd: () => setStartLongPress(false),
  };
}
import useLongPress from './useLongPress';

function MyComponent (props) {
  const backspaceLongPress = useLongPress(props.longPressBackspaceCallback, 500);

  return (
    <Page>
      <Button {...backspaceLongPress}>
        Click me
      </Button>
    </Page>
  );
};

完成它的好方法!如何传递带参数的回调?
2021-05-23 05:03:15
@SublimeYe 你是对的。但我不知道如何像你提到的那样用 useRef 改变它:(
2021-05-25 05:03:15
警告!每次用户触发操作时,这种方法都会触发不必要的重新渲染。如果您将其与stackoverflow.com/a/48057286/329879(使用类的)进行比较 - 您会看到类组件不会触发不必要的重新渲染。这可以通过使用 useRef 代替计时器来改进。计时器可用于跟踪按钮是否被按下。
2021-06-01 05:03:15
clearTimeout内部else没有做任何事情-timerId将永远是不确定的,因为它刚刚宣布永不分配(我们正在运行从头每种效果触发回调)。但你也不需要它,清理clearTimeout就足够了。
2021-06-08 05:03:15
优秀的最新答案!好东西。
2021-06-09 05:03:15

不错的钩子!但我想做一个小的改进。使用useCallback包事件处理程序。这确保这些不会在每次渲染时更改。

import { useState, useEffect, useCallback } from 'react';

export default function useLongPress(callback = () => {}, ms = 300) {
  const [startLongPress, setStartLongPress] = useState(false);

  useEffect(() => {
    let timerId;
    if (startLongPress) {
      timerId = setTimeout(callback, ms);
    } else {
      clearTimeout(timerId);
    }

    return () => {
      clearTimeout(timerId);
    };
  }, [callback, ms, startLongPress]);

  const start = useCallback(() => {
    setStartLongPress(true);
  }, []);
  const stop = useCallback(() => {
    setStartLongPress(false);
  }, []);

  return {
    onMouseDown: start,
    onMouseUp: stop,
    onMouseLeave: stop,
    onTouchStart: start,
    onTouchEnd: stop,
  };
}
@YoavHortman 感谢您指出我的错误。我修好了它。stop功能应该setStartLongPress(false):)
2021-05-24 05:03:15
很好的回答,一个小小的一点是,虽然,stopstart两个呼叫setStartLongPress(true),不应该将它们倒?
2021-05-25 05:03:15
我用大卫的回答指出了这一点,这里也是如此:clearTimeout内部else不做任何事情 -timerId将永远是未定义的,因为它只是被声明并且从未分配过(我们从头开始运行每个效果触发器的回调)。但你也不需要它,清理clearTimeout就足够了
2021-06-03 05:03:15
我如何在组件中使用它?
2021-06-05 05:03:15
您可以在 David 的答案中看到用法。
2021-06-08 05:03:15

基于@Sublime me 上面关于避免多次重新渲染的评论,我的版本不使用任何触发渲染的内容:

export function useLongPress({
  onClick = () => {},
  onLongPress = () => {},
  ms = 300,
} = {}) {
  const timerRef = useRef(false);
  const eventRef = useRef({});

  const callback = useCallback(() => {
    onLongPress(eventRef.current);
    eventRef.current = {};
    timerRef.current = false;
  }, [onLongPress]);

  const start = useCallback(
    (ev) => {
      ev.persist();
      eventRef.current = ev;
      timerRef.current = setTimeout(callback, ms);
    },
    [callback, ms]
  );

  const stop = useCallback(
    (ev) => {
      ev.persist();
      eventRef.current = ev;
      if (timerRef.current) {
        clearTimeout(timerRef.current);
        onClick(eventRef.current);
        timerRef.current = false;
        eventRef.current = {};
      }
    },
    [onClick]
  );

  return useMemo(
    () => ({
      onMouseDown: start,
      onMouseUp: stop,
      onMouseLeave: stop,
      onTouchStart: start,
      onTouchEnd: stop,
    }),
    [start, stop]
  );
}

它还提供两个onLongPressonClick并且将接收到的事件对象。

用法主要如前面所述,除了现在在对象中传递参数之外,所有参数都是可选的:

  const longPressProps = useLongPress({
    onClick: (ev) => console.log('on click', ev.button, ev.shiftKey),
    onLongPress: (ev) => console.log('on long press', ev.button, ev.shiftKey),
  });

// and later:
  return (<button {...longPressProps}>click me</button>);

这是最受欢迎的答案typescript版本,以防万一它对任何人都有用:

(它还解决了通过使用和克隆事件访问event委托事件中的属性的问题timeOute.persist()

使用LongPress.ts

import { useCallback, useRef, useState } from "react";
  
function preventDefault(e: Event) {
  if ( !isTouchEvent(e) ) return;
  
  if (e.touches.length < 2 && e.preventDefault) {
    e.preventDefault();
  }
};

export function isTouchEvent(e: Event): e is TouchEvent {
  return e && "touches" in e;
};

interface PressHandlers<T> {
  onLongPress: (e: React.MouseEvent<T> | React.TouchEvent<T>) => void,
  onClick?: (e: React.MouseEvent<T> | React.TouchEvent<T>) => void,
}

interface Options {
  delay?: number,
  shouldPreventDefault?: boolean
}

export default function useLongPress<T>(
  { onLongPress, onClick }: PressHandlers<T>,
  { delay = 300, shouldPreventDefault = true }
  : Options
  = {}
) {
  const [longPressTriggered, setLongPressTriggered] = useState(false);
  const timeout = useRef<NodeJS.Timeout>();
  const target = useRef<EventTarget>();

  const start = useCallback(
    (e: React.MouseEvent<T> | React.TouchEvent<T>) => {
      e.persist();
      const clonedEvent = {...e};
      
      if (shouldPreventDefault && e.target) {
        e.target.addEventListener(
          "touchend",
          preventDefault,
          { passive: false }
        );
        target.current = e.target;
      }

      timeout.current = setTimeout(() => {
        onLongPress(clonedEvent);
        setLongPressTriggered(true);
      }, delay);
    },
    [onLongPress, delay, shouldPreventDefault]
  );

  const clear = useCallback((
      e: React.MouseEvent<T> | React.TouchEvent<T>,
      shouldTriggerClick = true
    ) => {
      timeout.current && clearTimeout(timeout.current);
      shouldTriggerClick && !longPressTriggered && onClick?.(e);

      setLongPressTriggered(false);

      if (shouldPreventDefault && target.current) {
        target.current.removeEventListener("touchend", preventDefault);
      }
    },
    [shouldPreventDefault, onClick, longPressTriggered]
  );

  return {
    onMouseDown: (e: React.MouseEvent<T>) => start(e),
    onTouchStart: (e: React.TouchEvent<T>) => start(e),
    onMouseUp: (e: React.MouseEvent<T>) => clear(e),
    onMouseLeave: (e: React.MouseEvent<T>) => clear(e, false),
    onTouchEnd: (e: React.TouchEvent<T>) => clear(e)
  };
};