本章我们专门探讨 Svelte 事件处理。
我们来写一个简单的程序,界面上展示鼠标当前所在的坐标:
<script>
let m = { x: 0, y: 0 };
function handleMousemove(event) {
m.x = event.clientX;
m.y = event.clientY;
}
</script>
<style>
div { width: 100%; height: 100%; }
</style>
<div>
The mouse position is {m.x} x {m.y}
</div>
我们此前已经初步了解到,要在元素上监听任意事件,我们可以使用 on: 指令:
<div on:mousemove={handleMousemove}>
The mouse position is {m.x} x {m.y}
</div>
你也可以用内联的方式声明事件处理:
<div on:mousemove="{e => m = { x: e.clientX, y: e.clientY }}">
The mouse position is {m.x} x {m.y}
</div>
属性值上的双引号其实是可选的,只是某些环境下有助于语法突出显示。
在一些框架中,出于性能原因,尤其是在循环内部,可能会建议你避免使用内联事件处理。不过这建议不适用 Svelte,无论你选择哪种形式,编译器都将始终会做正确的事情。
DOM 事件处理程序允许使用修饰符来变更行为。例如,对某个处理程序使用once
修饰符后,这个处理程序只会运行一次:
<script>
function handleClick() {
alert('no more alerts')
}
</script>
<button on:click|once={handleClick}>Click me</button>
事件修饰符完整的列表:
preventDefault
:在运行事件处理程序之前先调用 event.preventDefault()
,这对客户端在表单处理时就很有用。stopPropagation
:调用 event.stopPropagation()
停止事件冒泡,防止事件传播到下一个元素。passive
:改进触摸/滚轮事件的滚动性能(Svelte 默认会自动在安全的地方添加它)。nonpassive
:显式设置 passive: false
。capture
:在 捕获 阶段就触发处理程序,而非 冒泡 阶段(参阅 MDN 文档)once
:在运行一次事件处理后,立即将其移除。self
:仅当 event.target 是元素本身时才触发事件处理程序。on:click|once|capture={...}
。组件也可以发送(dispath)事件。为此,需要创建一个事件分发器(dispatcher)。
为了展示组件事件,需要先写一个组件,假设是 Inner.svelte:
Inner.svelte
<script>
// setup code goes here
function sayHello() {
}
</script>
<button on:click={sayHello}>Click to say hello</button>
我们在 App.svelte 引入这个 Inner.svelte
组件:
App.svelte
<script>
import Inner from './Inner.svelte';
function handleMessage(event) {
alert(event.detail.text);
}
</script>
<Inner on:message={handleMessage}/>
你可以看到,在 App.svelte
中绑定了一个 on:message
自定义的事件名给 Inner
,这说明 Inner
组件需要能够发送这个事件,handleMessage
才有可能被调用。
如何在 Inner 中对外发送一个自定义的事件?—— 这就需要用到事件分发器 EventDispatcher
。
尝试修改一下 Inner.svelte
:
Inner.svelte
<script>
import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher();
function sayHello() {
dispatch('message', {
text: 'Hello!'
});
}
</script>
<button on:click={sayHello}>Click to say hello</button>
createEventDispatcher
必须在组件实例化时就调用它来创建分发器,你无法在组件一些特殊位置中创建(例如利用setTimeout
延迟几秒之后创建)。dispatch
将会关联到组件实例中。
注意:由于 on:message
指令,使得 App 组件得以监听由 Inner 组件发出的消息。
这个指令是一个以 on:
作为前缀的属性,后面跟着我们正在分发的事件名称(在当前例子叫 message
)。
不过就算没有这个属性,消息仍然会被分发,只是应用不会有任何响应而已。你可以尝试删除 on:message
,然后再点击按钮。
你也可以尝试修改事件的名称为其他任意名称。例如,在
Inner.svelte
中将dispatch('message')
改为dispatch('myevent')
,然后在App.svelte
中将属性on:message
改为on:myevent
。
与DOM事件不同,组件事件不会 冒泡。 如果要在某个深层嵌套的组件上监听事件,则中间组件必须 转发 该事件。
在这种情况下,我们具有与上一节相同的 App.svelte
和 Inner.svelte
,不过现在还需创建一个 Outer.svelte
,并且在 Outer.svelte
组件中添加 <Inner/>
。
Outer.svelte
<script>
import Inner from './Inner.svelte';
</script>
<Inner/>
解决这个问题的其中一种方案,是在 Outer.svelte
添加 createEventDispatcher
,然后监听 message
事件,并为其创建处理程序:
Outer.svelte
<script>
import Inner from './Inner.svelte';
import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher();
function forward(event) {
dispatch('message', event.detail);
}
</script>
<Inner on:message={forward}/>
不过如果层次过深,这么一层一层地转发枯燥乏味,Svelte 有一种等效的速写方式:on:message
事件指令不写其值,就代表“转发所有message
事件”,Svelte 自动生成与上方相同的代码替你包圆了:
<script>
import Inner from './Inner.svelte';
</script>
<Inner on:message/>
上一节学到的组件自定义事件转发,同样适用于 DOM。
下方包装了一个 CustomButton.svelte
闪亮按钮组件:
CustomButton.svelte
<style>
button {
height: 4rem;
width: 8rem;
background-color: #aaa;
border-color: #f1c40f;
color: #f1c40f;
font-size: 1.25rem;
background-image: linear-gradient(45deg, #f1c40f 50%, transparent 50%);
background-position: 100%;
background-size: 400%;
transition: background 300ms ease-in-out;
}
button:hover {
background-position: 0;
color: #aaa;
}
</style>
<button>Click me</button>
App.svelte
<script>
import CustomButton from './CustomButton.svelte';
function handleClick() {
alert('clicked');
}
</script>
<CustomButton on:click={handleClick}/>
作为一个纯粹的通用按钮的封装,CustomButton.svelte
不处理具体点击事件,而应该将事件发送给使用端去处理。
例如,当 <CustomButton>
的点击的事件,我们期望可以收到通知,为此,只需转发 CustomButton.svelte
中的 <button>
元素的 click
事件即可。
CustomButton.svelte
......
<button on:click>Click me</button>
如何响应这个点击事件,交由 App.svelte 接收到 click 事件后自行处理。
本章探讨了 Svelte 事件的全部内容,最简单也是最常用的 DOM 事件绑定。
通过使用 on:
指令,例如 on:click
,你可以直接内联一个函数的形式 on:click={ () => ... }
,又或者将函数提到外部的形式 on:click={ handler }
。两者的结果没有什么区别。
它同时也支持绑定组件的自定义事件,组件需要借助 createEventDispatcher()
方法将事件转发到使用端。
如果你正在编写一个 Svelte 组件库,那么事件转发是组件库创作者的必备工具箱,组件库里的组件一般不处理事件响应,而是应该将这个响应移交使用端处理。
不得不说,对比起 Vue 的写法,Vue 支持 @ 符号声明事件更为简洁一些,例如:
<button @click>Click me</button>