Svelte 前端开发笔记

近期用 Svelte 开发了一个小项目,记录在开发过程中遇到的一些问题。

逻辑块中的临时变量

通常为了简化代码,在 {#each}{#if} 块中需要用到临时变量。比如:

svelte{#each items as item}
    <a href="/user/{item.article.author.id}" title={item.article.author.fullName} use:link>
        {item.article.author.name}
    </a>
{/each}

上面代码中的 {item.article.author} 被多次引用,如果可以将其赋值给一个临时变量,那么代码可以简略些许。其实 Svelte 在某个版本中加入了 {@const} 指令可以实现临时变量,然而中文版的 Svelte 文档过于陈旧,我并没有找到相关的指令说明,直到后来翻阅官网的英文文档才找到。上面的代码可以简化为:

svelte{#each items as item}
    {@const author = item.article.author}
    <a href="/user/{author.id}" title={author.fullName} use:link>
        {author.name}
    </a>
{/each}

另外一种情况,循环块中的事件处理函数通常需要用到循环中的临时变量,可以利用闭包的特性将数据绑定到事件处理函数:

svelte<script lang="ts">
    let items = loadItemsFromSomewhere();

    function wrapArticleClick(article) {
        return () => {
            console.log(article.title);
        };
    }
</script>

{#each items as {article}}
    <button on:click={wrapArticleClick(article)}>{article.title}</button>
{/each}

路由参数变化时不会重新渲染组件

我在项目中使用的路由组件是 svelte-spa-router。之所以选择这款第三方路由倒也没什么特别原因,仅仅是因为它在 NPM 上的下载次数比较多而已。其定义路由映射表的代码如下:

typescriptimport Home from './views/Hello.svelte';

const routes = {
    '/hello/:hello': Hello,
};

export default routes;
routes.ts

该路由组件有个特性:就是当路由的参数变化时,它不会重新载入或重新渲染组件。虽然有点违背直觉,但是考虑到 Svelte 的响应式特性,这样的实现还是挺合理的。比如,下面的代码在路由参数变化时,页面内容不会变化:

svelte<script lang="ts">
    export let params = {};
    let hello: string = params['hello'];
</script>

<p>{hello}</p>
views/Hello.svelte

只要稍作修改就可以了:

svelte<script lang="ts">
    export let params = {};
    let hello: string;
    $: hello = params['hello'];
</script>

<p>{hello}</p>
views/Hello.svelte

单页应用 (SPA) 的局限性

这并非是 Svelte 的问题,而是所有 SPA 前端项目的通病。除了 SEO 不友好外,更新客户端状态也是需要考虑的棘手问题。对于传统的 Web 项目,当用户点击链接、页面发生跳转时,就相当于和服务器进行了一次交互,这时可以完成客户端状态的更新;而 SPA 项目如果不是用户主动刷新页面或进行与服务器的交互操作,是无法更新客户端状态的。这些客户端状态包括但不限于:用户登录状态、有时效性的 CSRF 令牌等。

目前想到如下几种解决方案:

  1. 使用 WebSocket 或长轮询等方式和服务器端保持长连接,实时获取状态变化;
  2. 定时向服务器发起获取当前状态请求并更新;
  3. 每次和服务器产生交互时,服务器返回额外的状态信息;
  4. 当和服务器交互发生异常时,则向服务器请求当前状态并更新。

方法 1 的实现方式比较复杂,且服务器的开销也比较大,除非需求对客户端状态变化非常敏感,普通应用似无必要。方法 2 既可以更新客户端状态,又可以保持会话,实现原理也简单。但是要注意的是,对于移动端的浏览器,当页面切换到后台时,页面的 setInterval() 脚本会停止运行。方法 3 需要对服务器所有接口进行再封装,如果变动接口牵一发动全身的话则得不偿失。方法 4 的实现最简单,客户端和服务器交互时,如果服务器响应 401 状态码,则表示登录失效,客户端跳转到登录页或另作处理;如果服务器响应 CSRF 令牌校验失败,则请求对应接口,获取最新的 CSRF 令牌,等等。

下一步是否应该考虑迁移到 SvelteKit 的静态站点生成 (Static Site Generation) 呢?