初步认识 Svelte 生命周期中的 tick

326 阅读2分钟

tick

内容来自 github

tick 不同于其他生命周期,我们可以在任意时候调用它。tick() 返回一个 Promise 对象,如果 pending state 没有变化,则该 Promise 会立即被 resolve;否则,当变化的 pending state 已经应用到 DOM 上时,该 Promise 才会被 resolve。

The tick function is unlike other lifecycle functions in that you can call it any time, not just when the component first initialises. It returns a promise that resolves as soon as any pending state changes have been applied to the DOM (or immediately, if there are no pending state changes).

当组件的 state 发生变化时,并不会立即应用到 DOM 上,svelte 将会在下一个 microtask 中,统一将所有发生变化的 state 应用到 DOM 上。这样的好处是提高性能,因为一个 state 变化后,在未到达下一个 microtask,还可以继续改变,或者取消改变。

举个例子:我们想要实现这么一个功能,当选中文本然后按下 TAB 按键时,将选中的文本大小写切换,并且保证鼠标依旧选中对应内容。

code.juejin.cn/pen/7279688…

<body>
  <textarea cols="60" rows="3"></textarea>

  <script>
    const textarea = document.querySelector('textarea')
    textarea.value = 'Select some text and hit the tab key to toggle uppercase'

    textarea.addEventListener('keydown', event => {
        if (event.key !== 'Tab') return;

        event.preventDefault();

        const { selectionStart, selectionEnd, value } = textarea;
        const selection = value.slice(selectionStart, selectionEnd);

        const replacement = /[a-z]/.test(selection)
            ? selection.toUpperCase()
            : selection.toLowerCase();

        textarea.value =
            value.slice(0, selectionStart) +
            replacement +
            value.slice(selectionEnd);

        textarea.selectionStart = selectionStart;
        textarea.selectionEnd = selectionEnd;
    })
  </script>

</body>

但对应到 svelte 中,我们就需要借助 tick() 了。

code.juejin.cn/pen/7279688…

<textarea
    value={text}
    on:keydown={handleKeydown}
    cols="60"
    rows="3"
/>

<script>
    import { tick } from "svelte"

    let text = `Select some text and hit the tab key to toggle uppercase`;

    async function handleKeydown(event) {
        if (event.key !== 'Tab') return;

        event.preventDefault();

        const { selectionStart, selectionEnd, value } = this;
        const selection = value.slice(selectionStart, selectionEnd);

        const replacement = /[a-z]/.test(selection)
            ? selection.toUpperCase()
            : selection.toLowerCase();

        text =
            value.slice(0, selectionStart) +
            replacement +
            value.slice(selectionEnd);

        /* 此时 text 的值并未应用到 DOM 上 */
        await tick()
        /* 当 await tick() 结束时,能够确保 text 已经被应用到 DOM上了 */
        this.selectionStart = selectionStart;
        this.selectionEnd = selectionEnd;
    }
</script>

首先,我们调用 event.preventDefault() 阻止了 tab 按键的默认行为,这个时候,如果我们什么都没有改变,那么选中文本点击 tab 时将不会发生任何事情。但是由于我们在按下 TAB 时更改了 textarea 文本的值,所以浏览器会自动取消选中的内容,然后将焦点移到末尾。在原生的实现中,我们可以直接为 textarea 指定 selectionStartselectionEnd 属性,来确保依旧选中对应的文本。

但是在 svelte 中情况有所不同,因为 svelte 中的执行顺序本质上不是同步的,在没有添加 await tick() 的时候,handleKeydown() 执行完成时,text 的值并未被应用到 DOM 上面。等到下一个 microtask 时,text 的值才会被应用到 DOM 上,但此时我们的 handleKeydown() 已经执行完了,它不会再次去重新设置 textareaselectionStartselectionEnd 属性。

当我们添加了 await tick() 后,handleKeydown() 执行到 await tick() 会停住,等待下一个 microtask 的到来。当到达下一个 microtask 时,变更的 pending state 将会被应用到 DOM 上,所以我们的 text 将会被应用到 DOM 上(此时浏览器会取消选中的内容)。因为 pending state 被应用到 DOM 上了,所以 handleKeydown() 会接着执行 await tick() 后面的内容。