面向 Angular 开发者的 Svelte

2025-06-07

面向 Angular 开发者的 Svelte

从 Angular 开发人员的角度简单介绍 Svelte

本文最初Giancarlo Buomprisco在Bits and Pieces上发表

Svelte是一个相对较新的用于构建 UI 的框架,由 Rich Harris(也是 Rollup 的创建者)开发。

Svelte 可能会以一种与您以前见过的截然不同的方式出现,这可能是一件好事。

Svelte 主要在两个方面给你留下深刻印象:它的速度和它的简单性。在本文中,我们将重点关注后者。

由于我的主要专业知识围绕着 Angular,因此我尝试使用 Svelte 复制我习惯的方法来学习 Angular 是很正常的。本文将要讨论的内容是:如何用Svelte来做Angular 的事情


实用技巧:使用**Bit **封装组件及其所有依赖项和设置。构建真正的模块化应用程序,实现更好的代码复用、更简单的维护和更低的开销。


注意:虽然我会表达我的偏好,但这并不是一个比较:对于使用 Angular 作为首选框架的人来说,这是一个简单而快速的 Svelte 介绍。

剧透警告:Svelte 很有趣。

组件📦

在 Svelte 中,每个组件都对应着其对应的文件:例如,组件 Button 的创建文件名为 Button.svelte。当然,在 Angular 中我们通常也会这么做,但这纯粹是一种惯例。

Svelte 组件使用单文件约定编写,它由 3 个部分组成:脚本、样式和模板,不必包装在其特定标签内。

让我们创建一个渲染“Hello World”的非常简单的组件。

导入组件

这与导入 JS 文件基本相同,只有一点区别:

  • 您需要明确引用带有 .svelte 扩展名的组件

  • 需要注意的是,你需要从脚本部分导入 Svelte 组件

    <script>
      import Todo from './Todo.svelte';
    </script>

    <Todo></Todo>
Enter fullscreen mode Exit fullscreen mode

💡从上面的代码片段可以清楚地看出,在 Svelte 中创建组件的代码行数非常少。当然,这其中有很多隐式和约束,但它足够简单,可以快速上手。玩得真棒。

基本语法📕

插值

Svelte 中的插值与 React 的相似度比与 Vue 或 Angular 的相似度更高:

    <script>
      let someFunction = () => {...}

    </script>

    <span>{ 3 + 5 }</span>
    <span>{ someFunction() }</span>
    <span>{ someFunction() ? 0 : 1 }</span>
Enter fullscreen mode Exit fullscreen mode

我已经习惯输入两次花括号,有时我会犯一些简单的错误,但这只是我个人的情况。

属性

将属性传递给组件也相当容易,不需要引号,甚至可以是 Javascript 表达式:

    // Svelte
    <script>
      let isFormValid = true;
    </script>

    <button disabled={!isFormValid}>Button</button>
Enter fullscreen mode Exit fullscreen mode

活动

监听事件的语法是 on:event={handler}。


    <script>
      const onChange = (e) => console.log(e);
    </script>

    <input on:input={onChange} />
Enter fullscreen mode Exit fullscreen mode

你可能注意到了,与 Angular 相反,我们不需要使用括号来调用函数。要将参数传递给函数,只需定义一个匿名函数即可:


    <input on:input={(e) => onChange(e, ‘a’)} />
Enter fullscreen mode Exit fullscreen mode

就可读性而言,我有两种看法:

  • 少打字,因为我们不需要括号和引号,这总是一件好事

  • 就可读性而言,我一直更喜欢 Angular,而不是 React,因此对我来说,它比 Svelte 的可读性略高一些。话虽如此,我还是要再次强调,我已经非常习惯它了,因此我的观点可能存在偏差。

结构指令

与 Vue 和 Angular 相反,Svelte 为模板内的循环和控制流提供了特殊的语法,而不是使用结构指令:

    {#if todos.length === 0}
      No todos created
    {:else}
      {#each todos as todo}
        <Todo {todo} /> 
      {/each}
    {/if}
Enter fullscreen mode Exit fullscreen mode

我非常喜欢这个。无需创建 HTML 节点,而且可读性也很棒。可惜的是,我的 Macbook 的英式键盘把 # 键放在了一个难以触及的位置,这让我的使用体验有点不顺畅。

输入

从其他组件设置和获取属性(或 @Input)就像从脚本中导出常量一样简单。好吧,这可能有点
令人困惑——让我们写一个例子来看看它有多简单:

    <script>
      export let todo = { name: '', done: false };
    </script>

    <p>
      { todo.name } { todo.done ? '✅' : '❌' }
    </p>
Enter fullscreen mode Exit fullscreen mode
  • 您可能已经注意到,我们用一个值初始化了 todo:如果消费者不为输入提供值,那么这将是默认值

接下来,我们创建将数据传递下去的容器组件:

    <script>
     import Todo from './Todo.svelte';

     const todos = [{
      name: "Learn Svelte",
      done: false
     },
     {
      name: "Learn Vue",
      done: false
     }];
    </script>

    {#each todos as todo}
      <Todo todo={todo}></Todo>
    {/each}
Enter fullscreen mode Exit fullscreen mode

与普通 JS 类似,todo={todo} 可以缩短并写成如下形式:

    <Todo {todo}></Todo>
Enter fullscreen mode Exit fullscreen mode

一开始我认为这太疯狂了,现在我认为这很聪明。

输出

要创建@Output,即从子组件到其父组件的通知,我们将使用 Svelte 的 createEventDispatcher。

  • 我们导入函数 createEventDispatcher 并将其返回值分配给名为 dispatch 的变量

  • 我们用两个参数调用 dispatch:它的名称和它的有效载荷(或“详细信息”)

  • 注意:我们在点击事件(on:click)上添加了一个函数,它将调用函数markDone

    <script>
     import { createEventDispatcher } from 'svelte';

     export let todo;

     const dispatch = createEventDispatcher();

     function markDone() {
      dispatch('done', todo.name);
     }
    </script>

    <p>
     { todo.name } { todo.done ? '✅' : '❌' } 

     <button on:click={markDone}>Mark done</button>
    </p>
Enter fullscreen mode Exit fullscreen mode

容器组件需要为分派的事件提供回调,以便我们可以将该待办事项对象标记为“完成”:

  • 我们创建一个名为 onDone 的函数

  • 我们将该函数赋值给组件名为 done 的事件。
    语法为 on:done={onDone}

    <script>
     import Todo from './Todo.svelte';

     let todos = [{
      name: "Learn Svelte",
      done: false
     },
     {
      name: "Learn Vue",
      done: false
     }];

     function onDone(event) {
      const name = event.detail;

    todos = todos.map((todo) => {
       return todo.name === name ? {...todo, done: true} : todo;
      });
     }
    </script>

    {#each todos as todo}
      <Todo {todo} on:done={onDone}></Todo>
    {/each}
Enter fullscreen mode Exit fullscreen mode

注意:为了触发变更检测,我们不会改变对象。相反,我们重新分配数组 todos 并替换标记为已完成的 todo。

💡这就是为什么 Svelte 被认为是真正的 *反应式*:只需重新分配变量,视图就会相应地重新渲染。

ngModel

Svelte 提供了一种特殊的语法 bind:={value},用于将某些变量绑定到组件的属性并使它们保持同步。

换句话说,它允许双向数据绑定:

    <script>
      let name = "";
      let description = "";

      function submit(e) {  // submit }
    </script>

    <form on:submit={submit}>
      <div>
        <input placeholder="Name" bind:value={name} />
      </div>

      <div> 
        <input placeholder="Description" bind:value={description} />
      </div>

      <button>Add Todo</button>
    </form>
Enter fullscreen mode Exit fullscreen mode

响应式语句

正如我们之前所见,Svelte 会对赋值做出反应并重新渲染视图。为了对另一个值的变化做出反应,我们可以使用响应式语句,这样另一个值就可以自动随之变化。

例如,让我们创建一个变量来显示是否已检查所有待办事项:

    let allDone = todos.every(({ done }) => done);
Enter fullscreen mode Exit fullscreen mode

不幸的是,由于我们从未重新分配 allDone,视图将不会被渲染。让我们用一个响应式语句来替换它,这样我们就可以使用 JavaScript 的“标签”了:

    $: allDone = todos.every(({ done }) => done);
Enter fullscreen mode Exit fullscreen mode

哦!这太奇特了。在你大喊“这太神奇了!”之前:你知道标签也是有效的 JavaScript 代码吗?

我们来看一个演示:

内容投影

内容投影也使用插槽,这意味着您可以命名一个插槽并将其投影到组件内的任何位置。

要简单地将作为内容传递的所有内容插入到您的组件中,您可以简单地使用特殊元素槽:

    // Button.svelte
    <script>
        export let type;
    </script>

    <button class.type={type}>
     <slot></slot>
    </button>

    // App.svelte
    <script>
      import Button from './Button.svelte';
    </script>

    <Button>
      Submit
    </Button>
Enter fullscreen mode Exit fullscreen mode

如你所见,字符串“Submit”将取代 。命名插槽要求我们为插槽分配一个名称:

    // Modal.svelte
    <div class='modal'>
     <div class="modal-header">
       <slot name="header"></slot>
     </div>

     <div class="modal-body">
      <slot name="body"></slot>
     </div>
    </div>

    // App.svelte
    <script>
      import Modal from './Modal.svelte';
    </script>

    <Modal>
     <div slot="header">
      Header
     </div>

     <div slot="body">
      Body
     </div>
    </Modal>
Enter fullscreen mode Exit fullscreen mode

生命周期钩子

Svelte 提供了 4 个生命周期钩子,它们从 svelte 包导入。

  • onMount,组件挂载时运行的回调

  • beforeUpdate ,在组件更新之前运行的回调

  • afterUpdate ,组件更新运行的回调

  • onDestroy,组件被销毁时运行的回调

onMount 是一个接受回调函数的函数,当组件挂载到 DOM 上时,该回调函数会被调用。简而言之,它可以与 ngOnInit 关联。

如果从回调返回一个函数,则当组件被卸载时将调用该函数。

    <script>

    import { 
      onMount, 
      beforeUpdate, 
      afterUpdate, 
      onDestroy 
    } from 'svelte';

    onMount(() => console.log('Mounted', todo));
    afterUpdate(() => console.log('Updated', todo));
    beforeUpdate(() => console.log('Going to be updated', todo));
    onDestroy(() => console.log('Destroyed', todo));

    </script>
Enter fullscreen mode Exit fullscreen mode

💡 需要注意的是,当 onMount 被调用时,它的输入已经被初始化。这意味着,当我们在上面的代码片段中记录 todo 时,它就已经被定义了。

状态管理

Svelte 的状态管理极其简单有趣,这或许也是我最喜欢它的地方。抛开 Redux 的样板代码,让我们看看如何构建一个可以存储和操作待办事项的 store。

可写存储

我们可以做的第一件事是从 svelte/store 包中导入 writable,并将初始状态传递给函数

    import { writable } from 'svelte/store';

    const initialState = [{
      name: "Learn Svelte",
      done: false
    },
    {
      name: "Learn Vue",
      done: false
    }];

    const todos = writable(initialState);
Enter fullscreen mode Exit fullscreen mode

通常,我会将其存储在名为 todos.store.js 的文件中,并导出可写存储,以便容器组件可以更新它。

你可能已经注意到,todos 现在是一个可写对象,而不是数组。为了检索存储的值,我们将使用一些 Svelte 魔法:

  • 通过在商店名称前面加上 $,我们可以直接访问商店的价值!

💡这意味着,所有对 todos 的引用现在都将是 $todos:

    {#each $todos as todo}
      <Todo todo={todo} on:done={onDone}></Todo>
    {/each}
Enter fullscreen mode Exit fullscreen mode

设置状态

可以通过调用方法 set 来设置可写存储的状态,该方法将强制将状态设置为传递的值:

    const todos = writable(initialState);

    function removeAll() {
      todos.set([]);
    }
Enter fullscreen mode Exit fullscreen mode

更新状态

为了根据当前状态更新存储(在本例中为 todos 存储),我们可以调用函数 update 并传入一个回调函数。回调函数的返回值就是传递给存储的新状态:

让我们重写上面定义的函数 onDone:

    function onDone(event) {
      const name = event.detail;

      todos.update((state) => {
        return state.map((todo) => {
           return todo.name === name ? {...todo, done: true} : todo;
        });
      });
     }
Enter fullscreen mode Exit fullscreen mode

好吧,我知道了,我在组件里写了一个 Reducer。你说这不酷吗?我们把它移到 store 文件中,然后导出一个函数来简单地更新状态。

    // todos.store.js

    export function markTodoAsDone(name) {
      const updateFn = (state) => {
        return state.map((todo) => {
           return todo.name === name ? {...todo, done: true} : todo;
        });
      });

      todos.update(updateFn);
    }

    // App.svelte

    import { markTodoAsDone } from './todos.store';

    function onDone(event) {
      const name = event.detail;
      markTodoAsDone(name);
    }
Enter fullscreen mode Exit fullscreen mode

监听值的变化

我们可以使用 .subscribe 方法来监听 store 的值变化。不过需要注意的是,虽然界面看起来很相似,但 store 并不是一个可观察对象。

    const subscription = todos.subscribe(console.log);

    subscription(); // unsubscribe subscription by calling it
Enter fullscreen mode Exit fullscreen mode

💡 Svelte 的 store 包还提供了另外两个实用程序,称为 readable 和 derivable。

可观察对象

哦,这就是你期待已久的部分!你会很高兴地知道,Svelte 最近添加了对 RxJS 和 ECMAScript Observable 提案的支持。

作为一名 Angular 开发者,我非常习惯于响应式编程,如果没有异步管道之类的工具,那真是太可惜了。但 Svelte 再次让我惊喜。

让我们看看两者如何协同工作:我们将呈现使用关键字“Svelte”搜索的 Github 存储库列表。

您可以将下面的代码片段粘贴到 Svelte REPL 中,它就可以正常工作:

    <script>
     import rx from "[https://unpkg.com/rxjs/bundles/rxjs.umd.min.js](https://unpkg.com/rxjs/bundles/rxjs.umd.min.js)";
     const { pluck, startWith } = rx.operators;
     const ajax = rx.ajax.ajax;

     const URL = `[https://api.github.com/search/repositories?q=Svelte`](https://api.github.com/search/repositories?q=Svelte`);

     const repos$ = ajax(URL).pipe(
        pluck("response"),
        pluck("items"),
        startWith([])
     );
    </script>

    {#each $repos$ as repo}
      <div>
        <a href="{repo.url}">{repo.name}</a>
      </div>
    {/each}

    // Angular's implementation
    <div *ngFor="let repo of (repos$ | async)>
      <a [attr.href]="{{ repo.url }}">{{ repo.name }}</a>
    </div>
Enter fullscreen mode Exit fullscreen mode

💡 您可能已经注意到,我在可观察的 repos$ 前面添加了 $,Svelte 将自动渲染它!

我的 Svelte 愿望清单 🧧

Typescript 支持

作为一名 Typescript 爱好者,我忍不住希望能够编写类型化的 Svelte。我已经习惯了这种写法,以至于我不断地输入代码,然后不得不重新输入。我真心希望 Svelte 能尽快支持 Typescript,因为我猜想,如果大家在 Angular 环境下使用 Svelte,Typescript 肯定是大家的心愿。

约定和编码指南

能够在视图中渲染脚本块中的任何变量,这功能非常强大,但在我看来,这可能会造成一些混乱。我希望 Svelte 社区能够制定一套规范和指南,帮助开发人员保持文件的整洁易懂。

社区支持

Svelte 是一个很棒的项目。有了更多社区支持,例如第三方软件包、支持者、博客文章等等,它就能腾飞,成为我们如今所享受的优秀前端环境之外的又一个成熟且可选的版本。

最后的话

虽然我不太喜欢之前的版本,但 Svelte 3 给我留下了相当深刻的印象。它简单、小巧(但功能全面)而且充满乐趣。它如此与众不同,甚至让我想起了我第一次从 jQuery 过渡到 Angular 时的感受,这真是令人兴奋。

无论你选择哪种框架,学习 Svelte 可能只需要几个小时。一旦你掌握了基础知识以及与你习惯的写作风格的区别,编写 Svelte 就会变得非常容易。


如果您需要任何澄清,或者您认为某些内容不清楚或错误,请发表评论!

希望你喜欢这篇文章!如果喜欢,请在MediumTwitter上关注我,获取更多关于前端、Angular、RxJS、Typescript 等的文章!

文章来源:https://dev.to/gc_psk/svelte-for-angular-developers-3gfb
PREV
使用 PassportJS 构建 NodeJS Web 应用进行身份验证
NEXT
深入理解 RxJS Subjects 深入理解 RxJS Subjects Subject 是一种特殊的可观察对象,允许将值多播给多个观察者。Subjects 类似于 EventEmitters。