事件
为了使 Web 应用程序具有交互性,需要一种方法来响应用户事件。这可以通过在 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
处理程序相关的代码不会加载到 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 处理程序提供了一个 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 的事件监听器。这在你想创建自己的使用钩子或在编译时不知道事件名称时很有用。
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>
);
});