メインコンテンツまでスキップ

useEventListener

window や document などの任意のターゲットのイベントを、コンポーネントがマウントされている間だけ購読します。

Source
Loading...
function App() {
  const [listening, setListening] = useState(true)
  const [key, setKey] = useState('-')

  // A function, so that `window` is read in the browser and not while the
  // page renders on the server. `null` listens on nothing.
  useEventListener(
    () => (listening ? window : null),
    'keydown',
    (event) => setKey(event.key === ' ' ? 'Space' : event.key),
  )

  return (
    <div style={{ userSelect: 'none' }}>
      <div
        style={{
          padding: '2rem',
          marginBottom: '0.5rem',
          border: '1px solid currentColor',
          borderRadius: 8,
          textAlign: 'center',
        }}
      >
        {listening ? 'Press any key' : 'Not listening'}
      </div>
      <div>last key: {key}</div>
      <label>
        <input
          type="checkbox"
          checked={listening}
          onChange={(event) => setListening(event.target.checked)}
        />{' '}
        Listen on window
      </label>
    </div>
  )
}

インポート​

import { useEventListener } from '@tremolo-ui/react'

使い方​

useEventListener(target, 'keydown', handler, options)

イベント名でハンドラの型が決まります。'keydown' なら KeyboardEvent を受け取ります。

引数はどれもインラインで書けます。 ハンドラはイベントのたびに読み直され、ターゲットとオプションは参照の同一性ではなく、解決した中身で比べます。リスナを付け直すのは、要素・イベント名・オプションのどれかが実際に変わったときだけです。

ターゲット​

ターゲットは EventTarget、null、またはそのどちらかを返す関数です。

  • null は何も購読しません。しばらく購読をやめたいときに渡します
  • 関数はレンダーのたびに、ブラウザで呼ばれます。 ref の先の要素を渡すときや、サーバーでもレンダーするページで window / document を渡すときに使います。どちらもレンダー中のサーバーには存在しません
const ref = useRef(null)
useEventListener(() => ref.current, 'pointerenter', handler)
useEventListener(() => window, 'resize', handler)

既定のターゲットはありません。document や window も名前で渡してください。

オプション​

第 4 引数は addEventListener のオプションです。capture を表す真偽値か、capture / once / passive を持つオブジェクトを渡します。リスナを外すタイミングは hook が決めるので、signal は受け付けません。

useEventListener(() => window, 'touchmove', handler, { passive: false })

Svelte と Vue​

この hook は React だけのものです。どちらのフレームワークにも同じことをする手段が既にあるので、@tremolo-ui/svelte と @tremolo-ui/vue には置いていません。

<svelte:window> と <svelte:document> は、コンポーネントがマウントされている間だけ購読します。

<svelte:window onkeydown={(event) => (key = event.key)} />

それ以外のターゲットや、条件が成り立つ間だけ購読したいときは、svelte/events の on() を $effect から返します。on() はリスナを外す関数を返すので、それがそのまま effect の cleanup になります。

import { on } from 'svelte/events'

$effect(() => {
if (!listening) return
return on(window, 'keydown', (event) => (key = event.key))
})

途中でリスナを外す​

戻り値の関数はリスナを外します。ターゲット・イベント名・オプションが変わるまで外れたままなので、再レンダーしても戻りません。

const stop = useEventListener(() => window, 'pointermove', (event) => {
if (done(event)) stop()
})

オンとオフを切り替えるものなら、ターゲットを null にするほうを勧めます。リスナがあるかどうかがレンダーに書かれるためです。