事件
為了讓網頁應用程式具有互動性,需要一種方法來回應使用者事件。 這是透過在 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 👋">
視窗和文件事件
到目前為止,討論的重點一直是監聽來自元素的事件。有些事件(例如 scroll
和 mousemove
)需要在 window
或 document
上監聽。Qwik 允許通過在監聽事件時提供 document:on
和 window:on
前綴來實現這一點。
window:on
/document:
前綴用於在元件的當前 DOM 位置註冊事件,同時允許它從 window
/document
接收事件。這有兩個優點
- 事件可以在您的 JSX 中以聲明方式註冊。
- 元件銷毀時會自動清除事件(無需顯式記賬和清除)。
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>
);
});