イベント

ウェブアプリケーションをインタラクティブにするには、ユーザーイベントに応答する方法が必要です。これは、JSXテンプレートにコールバック関数を登録することで行います。イベントハンドラは、`on{EventName}$`属性を使用して登録されます。たとえば、`onClick$`属性は、`click`イベントを listen するために使用されます。

<button onClick$={() => alert('CLICKED!')}>click me!</button>

インラインハンドラ

次の例では、`<button>`要素の`onClick$`属性を使用して、`<button>`によって`click`イベントが発生するたびにコールバック`() => store.count++`が実行されることをQwikに知らせます。

import { component$, useSignal } from '@builder.io/qwik';
 
export default component$(() => {
  const count = useSignal(0);
 
  return (
    <button onClick$={() => count.value++}>
      Increment {count.value}
    </button>
  );
});

また、`bind:propertyName`を使用して、シグナルと入力要素間の双方向バインディングを簡便に行うこともできます。

`onClick$`が`$`で終わっていることに注意してください。これは、オプティマイザーと開発者の両方に対する、この場所で特別な変換が発生するというヒントです。`$`接尾辞の存在は、ここに遅延読み込みの境界があることを意味します。`click`ハンドラに関連付けられたコードは、ユーザーがクリックイベントをアクティブにするまで、JavaScript仮想マシン(VM)にロードされません。ただし、最初のインタラクション中の遅延を回避するために、ブラウザキャッシュに積極的にロードされます。

実際のアプリケーションでは、リスナーは複雑なコードを参照することがあります。遅延読み込みの境界(`$`を使用)を作成することで、Qwikはクリックリスナーの背後にあるすべてのコードをツリーシェイクし、ユーザーがボタンをクリックするまで読み込みを遅延させることができます。

イベントハンドラの再利用

複数の要素またはイベントに同じイベントハンドラを再利用するには、イベントハンドラを`@builder.io/qwik`からエクスポートされた`$()`関数でラップする必要があります。これは、イベントハンドラを`QRL`に変換します。

import { component$, useSignal, $ } from '@builder.io/qwik';
 
export default component$(() => {
  const count = useSignal(0);
  const increment = $(() => count.value++);
  return (
    <>
      <button onClick$={increment}>Increment</button>
      <p>Count: {count.value}</p>
    </>
  );
});

**注意:** イベントハンドラを抽出する場合は、手動で`$(...handler...)`でラップする必要があります。これにより、遅延的にアタッチされることが保証されます。

複数のイベントハンドラ

同じイベントに複数のイベントハンドラを登録するには、`on{EventName}$`属性にイベントハンドラの配列を渡します。

import { component$, useSignal, $ } from '@builder.io/qwik';
 
export default component$(() => {
  const count = useSignal(0);
  const print = $((ev) => console.log('CLICKED!', ev));
  const increment = $(() => count.value++);
 
  // The button when clicked will print "CLICKED!" to the console, increment the count and send an event to Google Analytics.
  return (
    <button
      onClick$={[print, increment, $(() => {
        ga.send('click', { label: 'increment' });
      })]}
    >
      Count: {count.value}
    </button>
  );
});

イベントオブジェクト

イベントハンドラの最初の引数は、`Event`オブジェクトです。このオブジェクトには、ハンドラをトリガーしたイベントに関する情報が含まれています。たとえば、`click`イベントの`Event`オブジェクトには、マウスの位置とクリックされた要素に関する情報が含まれています。MDNドキュメントで各DOMイベントの詳細を確認できます

import { component$, useSignal } from '@builder.io/qwik';
 
export default component$(() => {
  const position = useSignal<{ x: number; y: number }>();
  return (
    <div
      onClick$={(event) => (position.value = { x: event.x, y: event.y })}
      style="height: 100vh"
    >
      <p>
        Clicked at: ({position.value?.x}, {position.value?.y})
      </p>
    </div>
  );
});

非同期イベント

Qwikの非同期性のため、実装がJavaScript VMにまだロードされていない場合、イベントハンドラの実行が遅延する可能性があります。その結果、Eventオブジェクトの次のAPIは機能しません。

  • event.preventDefault()
  • event.currentTarget

デフォルト動作の防止

イベント処理は非同期であるため、`event.preventDefault()`を使用できません。これを解決するために、Qwikは`preventdefault:{eventName}`属性を介してデフォルト動作を防止する宣言的な方法を導入しています。

import { component$ } from '@builder.io/qwik';
 
export default component$(() => {
  return (
    <a
      href="/docs"
      preventdefault:click // This will prevent the default behavior of the "click" event.
      onClick$={() => {
        // event.PreventDefault() will not work here, because handler is dispatched asynchronously.
        alert('Do something else to simulate navigation...');
      }}
    >
      Go to docs page
    </a>
  );
});

イベントターゲット

イベント処理は非同期であるため、`event.currentTarget`を使用できません。これを解決するために、Qwikハンドラは2番目の引数として`currentTarget`を提供します。

import { component$, useSignal } from '@builder.io/qwik';
 
export default component$(() => {
  const currentElm = useSignal<HTMLElement|null>(null);
  const targetElm = useSignal<HTMLElement|null>(null);
 
  return (
    <section onClick$={(event, currentTarget) => {
      currentElm.value = currentTarget;
      targetElm.value = event.target as HTMLElement;
    }}>
      Click on any text <code>target</code> and <code>currentElm</code> of the event.
      <hr/>
      <p>Hello <b>World</b>!</p>
      <hr/>
      <ul>
        <li>currentElm: {currentElm.value?.tagName}</li>
        <li>target: {targetElm.value?.tagName}</li>
      </ul>
    </section>
  );
});

**注意:** DOMの`currentTarget`は、イベントリスナーがアタッチされた要素を指します。上記の例では、常に`<SECTION>`要素になります。

同期イベント処理

場合によっては、一部の API を同期的に使用する必要があるため、イベントを従来の方法で処理する必要があります。たとえば、dragstart イベントは同期的に処理する必要があるため、Qwik の遅延コード実行と組み合わせることはできません。

これを行うには、useVisibleTask を活用して、DOM API を直接使用してプログラムでイベントリスナーを追加できます。

import { component$, useSignal, useVisibleTask$ } from '@builder.io/qwik';
 
export default component$(() => {
  const draggableRef = useSignal<HTMLElement>();
  const dragStatus = useSignal('');
 
  useVisibleTask$(({ cleanup }) => {
    if (draggableRef.value) {
      // Use the DOM API to add an event listener.
      const dragstart = () => (dragStatus.value = 'dragstart');
      const dragend = () => (dragStatus.value = 'dragend');
 
      draggableRef.value!.addEventListener('dragstart', dragstart);
      draggableRef.value!.addEventListener('dragend', dragend);
      cleanup(() => {
        draggableRef.value!.removeEventListener('dragstart', dragstart);
        draggableRef.value!.removeEventListener('dragend', dragend);
      });
    }
  });
 
  return (
    <div>
      <div draggable ref={draggableRef}>
        Drag me!
      </div>
      <p>{dragStatus.value}</p>
    </div>
  );
});

注意 VisibleTask を使用してイベントをリッスンすることは、ブラウザでコードの積極的な実行を引き起こし、再開可能性を無効にするため、Qwik ではアンチパターンです。他に選択肢がない場合にのみ使用してください。JSX を使用してイベントをリッスンする必要があります。たとえば、<div onClick$={...}> のようにします。または、プログラムでイベントをリッスンする必要がある場合は、useOn(...) イベントメソッドの使用を検討してください。

カスタムイベントプロップ

コンポーネントを作成する場合、実際の DOM イベントではなく単なるコールバックであっても、イベントハンドラーに似たカスタムイベントプロップを渡すと便利なことがよくあります。Qwik のコンポーネント境界は、オプティマイザがそれらを個別のチャンクに分割できるように、シリアライズ可能である必要があります。関数は、QRL に変換されない限り、シリアライズできません。

たとえば、HTML ではデフォルトで実行できないトリプルクリックイベントをリッスンするには、onTripleClick$ カスタムイベントプロップを作成する必要があります。

import { component$, Slot, useStore } from '@builder.io/qwik';
 
export default component$(() => {
  return (
    <Button onTripleClick$={() => alert('TRIPLE CLICKED!')}>
      Triple Click me!
    </Button>
  );
});
 
type ButtonProps = {
  onTripleClick$: QRL<() => void>;
};
 
export const Button = component$<ButtonProps>(({ onTripleClick$ }) => {
  const state = useStore({
    clicks: 0,
    lastClickTime: 0,
  });
  return (
    <button
      onClick$={() => {
        // triple click logic
        const now = Date.now();
        const timeBetweenClicks = now - state.lastClickTime;
        state.lastClickTime = now;
        if (timeBetweenClicks > 500) {
          state.clicks = 0;
        }
        state.clicks++;
        if (state.clicks === 3) {
          // handle custom event
          onTripleClick$();
          state.clicks = 0;
        }
      }}
    >
      <Slot />
    </button>
  );
});

onTripleClick$: QRL<() => void>;QRL 型が使用されていることに注意してください。これは、関数を $() でラップするようなものですが、型レベルで行われます。const greet = $(() => "hi 👋"); と記述し、「greet」にカーソルを合わせると、「greet」の型が QRL<() => "hi 👋"> であることがわかります。

ウィンドウイベントとドキュメントイベント

これまでの説明は、要素から発生するイベントのリッスンに焦点を当てていました。window または document でリッスンする必要がある scrollmousemove などのイベントがあります。Qwik では、イベントをリッスンする際に document:on および window:on プレフィックスを提供することで、これを可能にします。

window:on/document: プレフィックスは、コンポーネントの現在の DOM の場所にイベントを登録するために使用されますが、window/document からイベントを受信できます。これには 2 つの利点があります。

  1. イベントは JSX で宣言的に登録できます。
  2. コンポーネントが破棄されると、イベントは自動的にクリーンアップされます(明示的なブックキーピングとクリーンアップは必要ありません)。

useOn[window|document] フック

  • useOn(): 現在のコンポーネントのルート要素でイベントをリッスンします。
  • useOnWindow(): window オブジェクトでイベントをリッスンします。
  • useOnDocument(): document オブジェクトでイベントをリッスンします。

useOn[window|document]() フックは、コンポーネントレベルでプログラムによって DOM ベースのイベントリスナーを追加します。これは、独自の use フックを作成する場合や、コンパイル時にイベント名がわからない場合に役立ちます。

import { $, component$, useOnDocument, useStore } from '@builder.io/qwik';
 
// Assume reusable use method that does not have access to JSX
// but needs to register event handlers.
function useMousePosition() {
  const position = useStore({ x: 0, y: 0 });
  useOnDocument(
    'mousemove',
    $((event) => {
      const { x, y } = event as MouseEvent;
      position.x = x;
      position.y = y;
    })
  );
  return position;
}
 
export default component$(() => {
  const pos = useMousePosition();
  return (
    <div>
      MousePosition: ({pos.x}, {pos.y})
    </div>
  );
});

コントリビューター

このドキュメントの改善に貢献してくれたすべてのコントリビューターに感謝します!

  • voluntadpear
  • the-r3aper7
  • RATIU5
  • manucorporat
  • nnelgxorz
  • adamdbradley
  • hamatoyogi
  • fleish80
  • cunzaizhuyi
  • Pika-Pool
  • mhevery
  • AnthonyPAlicea
  • amatiash
  • harishkrishnan24
  • fabian-hiller
  • igorbabko
  • mrhoodz
  • julianobrasil
  • maiieul
  • Balastrong
  • Jemsco