Event Handling
In frontend development, user interactions with pages are often event-driven. qingkuai provides concise and intuitive event binding syntax, allowing you to easily add click, input, keyboard and other event handling logic to elements, helping achieve responsive user interaction experiences.
In qingkuai, attributes starting with @
are used to bind event handlers, for example @click
listens for click events. This syntax is similar to HTML's onclick
attribute but more powerful, supporting interpolation blocks to write arbitrary expressions as event handling logic - not only more concise but also more expressive.
Binding Events
This code binds a click event to a button element. When clicked, the handleAddCount
method will be called:
<lang-js>
let count = 0
function handleAddCount(){
// When modifying reactive variables,
// the content of p element will be automatically updated
count++
}
</lang-js>
<p>current count: {count}</p>
<button @click={handleAddCount}>Add Count</button>
Functions like handleAddCount
are called event handlers. They can declare parameters to receive event objects, consistent with native addEventListener
behavior:
- js
- ts
function handleAddCount(e) {
count++
// true, both point to the clicked button element
console.log(e.target === this)
}
function handleAddCount(this: HTMLButtonElement, e: MouseEvent) {
count++
// true, both point to the clicked button element
console.log(e.target === this)
}
Like dynamic attributes, when event names match variable names the interpolation block can be omitted, making these two syntaxes equivalent:
<button @click></button>
<button @click={click}></button>
class
or for
attributes.
Inline Event Handlers
In the above example, the event handler only contains one line of code, so declaring a separate method seems unnecessary. We have two ways to simplify:
-
Use arrow functions directly in interpolation blocks:
qk<button @click={() => count++}>Add Count</button>
-
Use inline event handler syntax - writing JS/TS expressions directly in interpolation blocks:
qk<button @click={count++}>Add Count</button>
The second approach's inline handlers are compiled to code like:
<button @click={$args => count++}>Add Count</button>
So we can also access native event objects via $args
in inline handlers:
<button @click={$args => console.log($args.target)}>Add Count</button>
$args
as $event
might seem more logical. But for semantic consistency, $args better covers arbitrary parameters passed in component inline handlers we'll introduce later. Thus qingkuai uniformly uses $args as the default parameter name to reflect its versatility - representing both native events and arbitrary component parameters.
If you call other methods in inline handlers, qingkuai automatically binds their this
to the current element:
- js
- ts
<lang-js>
let count = 0
function handleAddCount(e) {
count++
// true, both point to the clicked button element
console.log(e.target === this)
}
</lang-js>
<p>current count: {count}</p>
<button @click={handleAddCount($args)}>Add Count</button>
<lang-ts>
let count = 0
function handleAddCount(this: HTMLButtonElement, e: MouseEvent) {
count++
// true, both point to the clicked button element
console.log(e.target === this)
}
</lang-ts>
<p>current count: {count}</p>
<button @click={handleAddCount($args)}>Add Count</button>
$args
is strictly typed - e.g. KeyboardEvent for @keydown
, MouseEvent for @click
, etc.
You may wonder: when is a handler inline vs regular? From qingkuai's compiler perspective:
Inline handlers are interpolation expressions needing function wrapping
When the expression is a single identifier, property access, function declaration or arrow function, it's considered complete and treated as a regular handler:
<!-- 普通事件处理器 -->
<tag @click={()=>{}}></tag>
<tag @click={identifier}></tag>
<tag @click={handlers.click}></tag>
<tag @click={function anonymous(){}}></tag>
<!-- 内联事件处理器 -->
<tag @click={count++}></tag>
<tag @click={handlers?.click()}></tag>
<tag @click={n > 10? yes(): no()}></tag>
Event Handler Modifiers
qingkuai allows appending modifiers after event names to simplify common interaction patterns. Currently supported modifiers:
-
Functional modifiers:
self: Only triggers if
event.target
is the element itself (doesn't block propagation);stop: Calls stopPropagation after handler;
prevent: Calls preventDefault after handler;
compose: Triggers during IME composition (e.g. Chinese input candidate selection);
once: Removes handler after first trigger (like addEventListener options.once);
capture: Triggers during capture phase (like options.capture);
passive: Indicates handler will never call
preventDefault
(mobile performance optimization);
-
Key modifiers (only trigger if specified keys are pressed, for keyboard events):
Basic keys: enter, tab, del, esc, up, down, left, right, space;
System keys: meta, alt, ctrl, shift;
-
Other modifiers:
compose: Triggers during IME composition (only for @input);
Example modifier usage:
<!--
The event handler only executes when clicking the
"Click" portion, and triggers just once
-->
<button @click|self|once={console.log("ok")}>
Click <span>Me</span>
</button>
<!--
The event handler only triggers when both alt and
shift keys are held down while clicking the button
-->
<button @click|alt|shift={console.log("ok")}> Click Me </button>