反应长记事件

Bin*_*iya 19 dom-events reactjs react-web create-react-app

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

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

sud*_*ang 36

您可以使用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>
    );
  }
}
Run Code Online (Sandbox Code Playgroud)

  • 如果我也想同时处理正常的鼠标单击怎么办?(如果答案很长,请让我知道。我将提出一个新问题) (6认同)
  • 您还需要添加onMouseLeave = {this.handleButtonRelease} (2认同)
  • 只是一个快速补充,正如steffen提到的,e对于onClick是未定义的,所以......在useLongPress中,有一行:“shouldTriggerClick &amp;&amp; !longPressTriggered &amp;&amp; onClick();” 我在“onClick()”中添加了“事件”(当然没有引号)。意思是,它应该看起来像这样:“shouldTriggerClick &amp;&amp; !longPressTriggered &amp;&amp; onClick(event);” 。现在,您甚至只需单击一下即可访问 e(e 将被定义)。 (2认同)

小智 32

在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);
    };
  }, [startLongPress]);

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

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

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

Run Code Online (Sandbox Code Playgroud)

  • 优秀,提供最新答案!好东西。 (4认同)
  • 警告!每次用户触发操作时,这种方法都会触发不必要的重新渲染。如果将此与 /sf/answers/3364010051/ (使用类的一个)进行比较 - 您会发现该类组件不会触发不必要的重新渲染。这可以通过使用 useRef 代替计时器来改进。计时器可用于跟踪按钮是否被按下。 (4认同)
  • 完成它的好方法!如何传递带有参数的回调? (2认同)

Huo*_*yen 11

好钩!但我想做一点改进。使用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);
    };
  }, [startLongPress]);

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

  return {
    onMouseDown: start,
    onMouseUp: stop,
    onMouseLeave: stop,
    onTouchStart: start,
    onTouchEnd: stop,
  };
}
Run Code Online (Sandbox Code Playgroud)

  • 好的答案是,有一小件事是,“停止”和“开始”都调用了“ setStartLongPress(true)”,它们不应该倒置吗? (2认同)

Dev*_*yam 8

基于@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]
  );
}
Run Code Online (Sandbox Code Playgroud)

它还提供两个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>);
Run Code Online (Sandbox Code Playgroud)


Mat*_* Lo 8

避免重新渲染的通用钩子

\n

这是我在生产中使用的东西,受到原始答案的启发。如果下面有错误,那么我想我在生产中遇到了错误!\xe2\x80\x8d\xe2\x99\x82\xef\xb8\x8f

\n

用法

\n

我想让钩子更加简洁,并在实现需要时允许可组合性(例如:添加快速输入与慢速输入,而不是单个回调)。

\n
const [onStart, onEnd] = useLongPress(() => alert(\'Old School Alert\'), 1000);\n\nreturn (\n  <button\n    type="button"\n    onTouchStart={onStart}\n    onTouchEnd={onEnd}\n  >\n    Hold Me (Touch Only)\n  </button>\n)\n
Run Code Online (Sandbox Code Playgroud)\n

执行

\n

它的实现比看起来更简单。只是多了很多行评论。

\n

我添加了一堆注释,因此如果您将其复制/粘贴到代码库中,您的同事可以在 PR 期间更好地理解它。

\n
import {useCallback, useRef} from \'react\';\n\nexport default function useLongPress(\n  // callback that is invoked at the specified duration or `onEndLongPress`\n  callback : () => any,\n  // long press duration in milliseconds\n  ms = 300\n) {\n  // used to persist the timer state\n  // non zero values means the value has never been fired before\n  const timerRef = useRef<number>(0);\n\n  // clear timed callback\n  const endTimer = () => {\n    clearTimeout(timerRef.current || 0);\n    timerRef.current = 0;\n  };\n\n  // init timer\n  const onStartLongPress = useCallback((e) => {\n    // stop any previously set timers\n    endTimer();\n\n    // set new timeout\n    timerRef.current = window.setTimeout(() => {\n      callback();\n      endTimer();\n    }, ms);\n  }, [callback, ms]);\n\n  // determine to end timer early and invoke the callback or do nothing\n  const onEndLongPress = useCallback(() => {\n    // run the callback fn the timer hasn\'t gone off yet (non zero)\n    if (timerRef.current) {\n      endTimer();\n      callback();\n    }\n  }, [callback]);\n\n  return [onStartLongPress, onEndLongPress, endTimer];\n}\n\n
Run Code Online (Sandbox Code Playgroud)\n

例子

\n

示例中使用 500ms 设置。GIF 中的自发圆圈显示了我按下按钮时的情况。

\n

例子

\n


Sun*_*tva 5

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

(它还解决了通过使用和克隆事件来访问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)
  };
};
Run Code Online (Sandbox Code Playgroud)