事件

為了讓網頁應用程式具有互動性,需要一種方法來回應使用者事件。 這是透過在 JSX 模板中註冊回呼函式來完成的。 事件處理器使用 on{EventName}$ 屬性註冊。 例如,onClick$ 屬性用於監聽 click 事件。

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

內嵌處理器

以下範例中,<button> 元素的 onClick$ 屬性用於讓 Qwik 知道,每當 <button> 觸發 click 事件時,都應執行回調函數 () => store.count++

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 處理器關聯的程式碼,直到使用者觸發 click 事件時才會載入到 JavaScript 虛擬機器 (VM) 中。但是,為了避免第一次互動時的延遲,它會被預先載入到瀏覽器快取中。

在實際應用中,監聽器可能會參考複雜的程式碼。透過建立延遲載入邊界(使用 $),Qwik 可以對 click 監聽器後面的所有程式碼進行樹狀搖樹(tree-shaking),並將其載入延遲到使用者點擊按鈕為止。

重複使用事件處理器

若要對多個元素或事件重複使用相同的事件處理器,您必須將事件處理器包裝到 @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 處理器將 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 👋">

視窗和文件事件

到目前為止,討論的重點一直是監聽來自元素的事件。有些事件(例如 scrollmousemove)需要在 windowdocument 上監聽。Qwik 允許通過在監聽事件時提供 document:onwindow:on 前綴來實現這一點。

window:on/document: 前綴用於在元件的當前 DOM 位置註冊事件,同時允許它從 window/document 接收事件。這有兩個優點

  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