Compile Svelte in Your Head(3)
Icon could not be loaded
8 min read
#writings#svelte

本文将介绍3个DOM相关的指令:

TOC

在Svelte模板如何使用

on:event name

使用on:指令在DOM元素或Svelte组件上绑定事件

<script>
function handleMouseMove(event) {}
function handleClick(event) {}
</script>
 
<!-- You can pass in as variable -->
<div on:mousemove={handleMouseMove} />
 
<!-- or you can inline the event handler -->
<div on:mousemove={event => { /*...*/ }} />
 
<!-- You can modify event handler with modifiers  -->
<div on:click|stopPropagation|once={handleClick}>

bind:property

将变量绑定到元素的属性上,更新变量将更新元素的属性。表单元素绑定的值被修改也会导致变量更新。

<script>
  let value, name, yes, text, selected;
</script>
 
<input bind:value />
<input bind:value={name} />
 
<textarea bind:value={text} />
 
<input type="checkbox" bind:checked={yes} />
 
<select bind:value={selected}>
    <option value={a}>a</option>
    <option value={b}>b</option>
    <option value={c}>c</option>
</select>

use: actions

use:指令用于绑定actionaction提供了另一种方式增强元素功能,当与第三方库一起使用时可使用。 action是一个函数,该函数将在绑定DOM元素创建时执行。 该函数返回一个对象,destroy在绑定到DOM元素从文档树中被移除时触发;当给action传递参数时,update在参数被更新时触发。

<script>
/** @type {import('svelte/action').Action}  */
function foo(node) {
    // the node has been mounted in the DOM
    return {
        destroy() {
        // the node has been removed from the DOM
        }
    };
}
</script>
 
<div use:foo />

Vanilla JavaScript

下面展示在不使用任何框架的情况下实现事件处理程序、绑定和action

Event handler

使用事件监听器

element.addEventListener('click', handleClick);

事件监听器接受一个可选的第三个参数,用于指定事件处理程序的特征:

element.addEventListener('click', handleClick, {
    capture: true, // triggered before any child element
    once: true, // triggered at most once
    passive: true, // indicates that will never call `preventDefault` to improve performance
});

要删除事件处理器,需要使用相同的事件、监听器、capture/useCapture

Bindings

绑定是在变量值和属性值之间进行同步。

要将变量同步到属性,需要观察变量的值,当变量变化时将其应用到元素的属性上。 另一方面,为了将元素的属性同步到变量,需要根据属性坚挺元素的事件,并在事件发生时更新变量的值。

// binding variable `checked` with the checkbox `checked` property
let checked;
let input = document.querySelector('#checkbox');
 
// synchronise variable `checked` to checkbox `checked` property
observe(checked, newValue => {
    input.checked = newValue;
});
 
// synchronise checkbox `checked` property to variable `checked`
// listen to `change` event for `checked` property
input.addEventListener('change', event => {
    checked = input.checked;
});

事件的名称和元素的属性名称可能各不相同,例如:<input type="text">可监听input事件中value属性变化,但复选框<input type="checkbox">监听checked属性的change事件

Action

action是一个在DOM元素创建时调用的函数,返回一个对象,有如下两个方法:

function actionFn(element, parameter) {
    return {
        update(newParameter) {},
        destroy() {},
    };
}
 
// When element is mounted onto the DOM
let parameter = 1;
const actionObj = actionFn(element, parameter);
 
// When parameter changes
parameter = 2;
actionObj.update(parameter);
 
// When element is removed from the DOM
actionObj.destroy();

The Compiled JavaScript

现在看经过svelte编译器编译后输出的代码:

on:

<script>
    function onChange() {}
</script>
<input on:change={onChange} />

output:

function create_fragment(ctx) {
    let input;
    let dispose;
 
    return {
        c() {
            input = element('input');
        },
        m(target, anchor, remount) {
            insert(target, input, anchor);
            if (remount) dispose();
            dispose = listen(input, 'change', /*onChange*/ ctx[0]);
        },
        d(detaching) {
            if (detaching) detach(input);
            dispose();
        },
    };
}
 
function instance($$self) {
    let i = 0;
    function onChange() {
        i++;
    }
    return [onChange];
}

事件修饰符

在绑定事件时可以传入事件修饰符,如:

<script>
let i=0;
function onClick() {
    i++;
}
</script>
 
<button on:click|preventDefault={onClick} />
<button on:change|stopPropagation={onClick} />
<button on:change|once={onClick} />
<button on:change|capture={onClick} />
 
<!-- Chain multiple modifiers -->
<button on:click|preventDefault|stopPropagation|once|capture={onClick} />

编译后的代码:

function create_fragment(ctx) {
  // ...
    return {
        c() { /* ... */ },
        m(target, anchor, remount) {
          // ...
            dispose = [
                listen(button0, "click", prevent_default(/*onClick*/ ctx[0])),
                listen(button1, "change", stop_propagation(/*onClick*/ ctx[0])),
                listen(button2, "change", /*onClick*/ ctx[0], { once: true }),
                listen(button3, "change", /*onClick*/ ctx[0], true),
                listen(
                    button4,
                    "click",
                    stop_propagation(prevent_default(/*onClick*/ ctx[0])),
                    { once: true, capture: true }
                ),
            ];
        },
    // ...
    };
}

preventDefault装饰器函数的示例实现:

function prevent_default(fn) {
    return function(event) {
        event.preventDefault();
        return fn.call(this, event);
    };
}

bind:

<script>
    let checked = false;
    function updateChecked() {
        checked = true;
    }
</script>
<input type="checkbox" bind:checked />

Output:

function create_fragment(ctx) {
    let input;
    let dispose;
    
    return {
        c() { /* ... */ },
        m(target, anchor, remount) {
            insert(target, input, anchor);
            input.checked = /*checked*/ ctx[0];
            if (remount) dispose();
            dispose = listen(input, 'change', /*input_change_handler*/ ctx[1]);
        },
        p(ctx, [dirty]) {
            if (dirty & /*checked*/ 1) {
                input.checked = /*checked*/ ctx[0];
            }
        },
        d(detaching) {
            if (detaching) detach(input);
            dispose();
        },
    };
}
 
function instance($$self, $$props, $$invalidate) {
    let checked = false;
    
    function updateChecked() {
        $$invalidate(0, (checked = true));
    }
    
    function input_change_handler() {
        checked = this.checked;
        $$invalidate(0, checked);
    }
    
    return [checked, input_change_handler];
}

use:

<script>
let i = '';
function action() {}
function updateI() {
    i++;
}
</script>
<div use:action={i} />

Output:

function create_fragment(ctx) {
  // ...
    let action_action;
    
    return {
        c() { /* ... */ },
        m(target, anchor, remount) {
            insert(target, div, anchor);
            if (remount) dispose();
            dispose = action_destroyer(
                (action_action = action.call(null, div, /*i*/ ctx[0]))
            );
        },
        p(ctx, [dirty]) {
            if (action_action && is_function(action_action.update) && dirty & /*i*/ 1)
                action_action.update.call(null, /*i*/ ctx[0]);
        },
        d(detaching) {
            if (detaching) detach(div);
            dispose();
        },
    };
}