Svelte props or component events?
在 Svelte 中,一个组件的事件具体行为如果必须由外部来定义的话,有两种解决办法,一种是用 props, 一种是组件的自定义事件。
使用 props 的写法:
<!-- App.svelte -->
<script>
import Inner from './Inner.svelte';
let name = 'Hamburger';
function alertName() {
alert(name);
}
</script>
<Inner name={name} sayMyName={alertName}/>
<!-- Inner.svelte -->
<script>
export let name;
export let sayMyName;
</script>
<form on:submit|preventDefault={sayMyName}>
<input bind:value={name}/>
<button type='submit'>Say it!</button>
</form>
使用 component events 的写法:
<!-- App.svelte -->
<script>
import Inner from './Inner.svelte';
function alertName(e) {
alert(e.detail.name);
}
</script>
<Inner on:say={alertName}/>
<!-- Inner.svelte -->
<script>
import { createEventDispatcher } from 'svelte';
let name = 'Hamburger';
const dispatch = createEventDispatcher();
function sayMyName() {
dispatch('say', {name});
}
</script>
<form on:submit|preventDefault={sayMyName}>
<input bind:value={name}/>
<button type='submit'>Say it!</button>
</form>
这里边很大的一个区别,是数据传递的方式。前者是将定义在外部 <App/>
的 name
作为 <Inner/>
组件的属性值传递到内部;后者则是将 <Inner/>
内部的属性通过 event.detail
暴露给外部。在多数情况下,这两种方法没有太大区别。我们很容易首选 props
的方案,因为从代码上看,明显这种方式写起来更方便,也更符合我们的直觉。我们都知道 JavaScript 里的 function 是可以被赋值给任意变量的。
但是当这个属性是个数组或其它对象时,二者的表现就可能很不一样,下面用一个数组来演示:
<!-- App.svelte -->
<script>
import Inner from './Inner.svelte';
let children = [];
function count() {
alert(`I have ${children.length} children!`);
}
</script>
<Inner children={children} countChildren={count}/>
<!-- Inner.svelte -->
<script>
export let children;
export let countChildren;
function addChild() {
children = [...children, {}];
}
</script>
<form on:submit|preventDefault={countChildren}>
<div>
<button type='button' on:click={addChild}>Add</button>
</div>
<ul>
{#each children as child}
<li><input bind:value={child.name}/></li>
{/each}
</ul>
<button type='submit'>Count</button>
</form>
<App/>
把 children
和 count()
函数都当作 props 传递给了 <Inner/>
. 这种情况下,我们期望的效果是:点击 3 次 Add 按钮之后,再点击 Count 按钮,会弹出一个 alert 框,显示 "I have 3 children!". 但结果显示的却是 "I have 0 children!".
问题出在 addChild()
这个函数。经过 Svelte 基础知识的学习,我们知道对于非基本类型的对象以及数组,要触发它的响应性(改变值时界面跟着变化),不能期望通过直接操作这个变量的方法来触发,而应该对该变量或者该变量的属性进行赋值操作。在 addChild()
这个例子里,就应该使用 children = [...children, {}]
的写法,而非 children.push({})
.
另一方面,同时我们也知道,将响应性的变量赋值给另一个变量时,通过第 2 个变量来改变其属性,将不会触发响应性。在该例子当中,<App/>
的 children
赋值给 <Inner/>
之后,addChild()
函数操作的是 <Inner/>
内部的 children
, 因此不会引起 <App/>
中 children
的变化,所以就有了我们看到的结果。
这时候,如果使用 component events, 将 children
作为 <Inner/>
内部的变量,通过自定义事件的方式传递到 <App/>
就可以避免这个问题:
<!-- App.svelte -->
<script>
import Inner from './Inner.svelte';
function count(e) {
alert(`I have ${e.detail.children.length} children!`);
}
</script>
<Inner on:count={count}/>
<!-- Inner -->
<script>
import { createEventDispatcher } from 'svelte';
let children = [];
const dispatch = createEventDispatcher();
function addChild() {
children = [...children, {}];
}
function countChildren() {
dispatch('count', {children});
}
</script>
<form on:submit|preventDefault={countChildren}>
<div>
<button type='button' on:click={addChild}>Add</button>
</div>
<ul>
{#each children as child}
<li><input bind:value={child.name}/></li>
{/each}
</ul>
<button type='submit'>Count</button>
</form>