从Angular开发人员的角度快速介绍Svelte

Svelte是由Rich Harris开发的相对较新的UI框架,他也是Rollup构建器的作者。 Svelte最有可能看起来与您之前所处理的完全不同,但这也许很好。 该框架的两个最令人印象深刻的功能是速度和简便性。 在本文中,我们将专注于第二篇。



由于我的主要开发经验与Angular有关,因此很自然地我会通过复制我已经熟悉的方法来学习Svelte。 这正是本文的主题:如何在Svelte中完成与Angular中相同的操作。


注意:尽管在某些情况下我会表达自己的偏爱,但本文并不是对框架的比较。 对于已经使用Angular作为其主要框架的人们,这是Svelte的快速简单的介绍。


警告剧透斯维尔特很有趣。


组成部分


在Svelte中,每个组件都与写入该文件的文件相关联。 例如,将通过命名Button.svelte文件来创建Button组件。 当然,我们通常在Angular中也做同样的事情,但是对于我们来说,这只是一个约定。 (在Svelte中,要导入的组件的名称也可能与文件名不一致-译者注)


Svelte组件是单个文件,由3个部分组成: scriptstyle和不需要包装在任何特殊标记中的模板。


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


hello_world


导入组件


通常,这类似于导入JS文件,但有一些警告:


  • 您必须显式指定.svelte组件文件.svelte
  • 组件导入<script>

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

从上面的代码片段中可以明显看出,在Svelte中创建组件的行数非常少。 当然,存在一些隐含性和局限性,但是与此同时,所有事情都足够简单,可以快速适应它。


基本语法


插补


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


 <script> let someFunction = () => {...} </script> <span>{ 3 + 5 }</span> <span>{ someFunction() }</span> <span>{ someFunction() ? 0 : 1 }</span> 

我习惯于使用双花括号,因此有时我会被封住,但也许我只有这个问题。


属性


将属性传递给组件也非常简单。 引号是可选的,可以使用任何Javascript表达式:


 //Svelte <script> let isFormValid = true; </script> <button disabled={!isFormValid}></button> 

大事记


事件处理程序的语法为: on:={}


 <script> const onChange = (e) => console.log(e); </script> <input on:input={onChange} /> 

与Angular不同,我们不需要在函数名称后使用括号来调用它。 如果需要将参数传递给处理程序,则只需使用匿名函数即可:


 <input on:input={(e) => onChange(e, 'a')} /> 

我对此类代码的可读性的观点是:


  • 我们必须减少打印量,因为我们不需要引号和括号-仍然很好。
  • 认真阅读。 我一直喜欢Angular而不是React,因此对我和Svelte来说更难。 但这只是我的习惯,我的看法有些偏颇。

结构指令


与Vue和Angular中的结构化指令不同,Svelte为模板内的循环和分支提供了一种特殊的语法:


 {#if todos.length === 0}    {:else} {#each todos as todo} <Todo {todo} /> {/each} {/if} 

我真的很喜欢 不需要其他HTML元素,就可读性而言,这看起来很棒。 不幸的是,我的Macbook的英式键盘布局中的#符号在一个难以接近的地方,这会对我使用这些结构带来负面影响。


输入属性


定义可以传递给组件的属性(类似于Angular中的@Input )就像使用export关键字从JS模块中导出变量一样容易。 也许一开始它可能会令人困惑-但是让我们写一个例子,看看它到底有多简单:


 <script> export let todo = { name: '', done: false }; </script> <p> { todo.name } { todo.done ? '✓' : '✕' } </p> 

  • 如您所见,我们初始化了todo属性及其值:如果未从父组件传递它,则默认情况下它将是该属性的值

现在,为此组件创建一个容器,该容器将向其传输数据:


 <script> import Todo from './Todo.svelte'; const todos = [{ name: " Svelte", done: false }, { name: " Vue", done: false }]; </script> {#each todos as todo} <Todo todo={todo}></Todo> {/each} 

与常规JS对象中的字段类似,可以将todo={todo}缩短并重写如下:


 <Todo {todo}></Todo> 

起初,这对我来说似乎很奇怪,但现在我认为它很棒。


输出属性


为了实现@Output指令的行为,例如,当父组件从子组件@Output任何通知时,我们将使用Svelte中可用的createEventDispatcher函数。


  • 导入createEventDispatcher函数并将其返回值分配给dispatch变量
  • dispatch功能具有两个参数: 事件名称数据 (将落入事件对象的detail字段中)
  • 我们markDone dispatch放在markDone函数中,该函数由click事件调用( on:click

 <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}></button> </p> 

在父组件中,您需要为done事件创建一个处理程序,以便可以在todo数组中标记必要的对象。


  • 创建onDone函数
  • 我们将此函数分配给在子组件中调用的事件处理程序,如下所示: on:done={onDone}

 <script> import Todo from './Todo.svelte'; let todos = [{ name: " Svelte", done: false }, { name: " 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} 

注意:为了开始检测对象的变化,我们不会突变对象本身。 取而代之的是,我们为todos变量分配一个新数组,所需任务的对象将已经更改为完成。


因此,斯维尔特(Svelte)被认为是真正的反应性通常将值分配给变量,表示的相应部分也会改变。


ngModel


Svelte具有特殊的bind:<>={}语法bind:<>={}用于将特定变量绑定到组件的属性,并使它们彼此同步。


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


 <script> let name = ""; let description = ""; function submit(e) { //    } </script> <form on:submit={submit}> <div> <input placeholder="" bind:value={name} /> </div> <div> <input placeholder="" bind:value={description} /> </div> <button> </button> </form> 

反应式


如前所述,Svelte响应为变量分配值并重绘视图。 您还可以使用反应式表达式来响应一个变量的值更改并更新另一个变量的值。


例如,让我们创建一个变量,该变量应向我们显示在todos数组中所有任务都标记为已完成:


 let allDone = todos.every(({ done }) => done); 

但是,更新数组时不会重绘视图,因为allDone变量的值仅分配一次。 我们将使用反应式表达式,该表达式同时使我们想起Javascript中“标签”的存在:


 $: allDone = todos.every(({ done }) => done); 

看起来很异国情调。 如果您觉得“魔术太多”,我提醒您标签是有效的Javascript


一个简短的演示说明了以上内容:
演示


内容注入


为了嵌入内容,还使用了插槽,这些插槽放置在组件内部的正确位置。


为了简单地显示在component元素内部传输的内容,使用了一个特殊的slot元素:


 // Button.svelte <script> export let type; </script> <button class.type={type}> <slot></slot> </button> // App.svelte <script> import Button from './Button.svelte'; </script> <Button>  </Button> 

在这种情况下, ""替换<slot></slot>元素。
命名的插槽将需要命名:


 // 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">  </div> <div slot="body">  </div> </Modal> 

生命周期挂钩


Svelte提供了4个从svelte包中导入的生命周期挂钩。


  • onMount-在DOM中安装组件时调用
  • beforeUpdate-在更新组件之前调用
  • afterUpdate-在组件更新后调用
  • onDestroy-从DOM中删除组件时调用

onMount函数将回调函数作为参数,当组件放置在DOM中时将调用该回调函数。 简而言之,它类似于ngOnInit钩子ngOnInit


如果回调函数返回另一个函数,则在从DOM中删除该组件时将调用该函数。


 <script> import { onMount, beforeUpdate, afterUpdate, onDestroy } from 'svelte'; onMount(() => console.log('', todo)); afterUpdate(() => console.log('', todo)); beforeUpdate(() => console.log('  ', todo)); onDestroy(() => console.log('', todo)); </script> 

重要的是要记住,在调用onMount其中包含的所有属性必须已经初始化。 也就是说,在上面的片段中, todo应该已经存在。


国家管理


在Svelte中管理您的状态非常简单,也许框架的这一部分比其他任何人更能同情我。 使用Redux时,您可能会忘记代码的冗长性。 例如,我们将在应用程序中创建用于存储和任务管理的存储。


可记录的保险库


首先,您需要从svelte/store包中导入writable存储writable ,并告诉其初始值initialState


 import { writable } from 'svelte/store'; const initialState = [{ name: " Svelte", done: false }, { name: " Vue", done: false }]; const todos = writable(initialState); 

通常,我将类似的代码放在一个单独的文件(如todos.store.jstodos.store.js导出存储变量,以便导入它的组件可以使用它。


显然, todos现在已成为存储库,不再是数组。 为了获得存储价值,我们将在Svelte中使用一些魔术:


  • 通过在存储变量的名称中添加$字符,我们可以直接访问其值!

因此,我们只需在代码中将所有对todos变量的引用替换为$todos


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

状态设定


可以通过调用set方法来指定可记录存储的新值,该方法必须根据传递的值强制更改状态:


 const todos = writable(initialState); function removeAll() { todos.set([]); } 

状态更新


要基于当前状态更新存储(在我们的示例中为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; }); }); } 

在这里,我直接在组件中使用了reducer,这是不好的做法。 我们将其移动到带有存储库的文件中,并从中导出一个函数,该函数将仅更新状态。


 // 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); } 

状态更改订阅


为了确定存储库中的值已更改,可以使用subscribe方法。 请记住,存储库不是observable对象,但是提供了类似的接口。


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

可观察的


如果这部分给您带来了最大的刺激,那么我很快就高兴了,不久前,RxJS支持添加到了Svelte,并且引入了Observable for ECMAScript。


作为Angular开发人员,我已经习惯于使用响应式编程,而缺少异步管道副本将非常不便。 但是斯维尔特在这里也让我感到惊讶。


让我们看一下这些工具如何协同工作的示例:在Github上显示存储库列表,该列表由关键字"Svelte"


您可以复制以下代码,然后在REPL中直接运行它:


 <script> import rx from "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`; 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: <div *ngFor="let repo of (repos$ | async)> <a [attr.href]="{{ repo.url }}">{{ repo.name }}</a> </div> --> 

只需在可观察变量repos$的名称中添加$符号,Svelte就会自动显示其内容。


我的愿望清单


打字稿支持


作为打字稿爱好者,我不禁希望在Svelte中使用类型。 我已经习惯了,有时我会迷失方向并在代码中排列类型,然后将其删除。 我真的希望Svelte能够尽快添加对Typescript的支持。 我认为该项目将在任何想使用Svelte并具有Angular经验的人的心愿单上。


协议和准则


呈现<script>块中任何变量的表示形式是该框架的一项非常强大的功能,但是我认为这会导致垃圾代码。 我希望Svelte社区将通过许多约定和准则来帮助开发人员编写简洁易懂的组件代码。


社区支持


Svelte是一个宏伟的项目,随着社区在编写第三方程序包,手册,博客文章等方面的投入不断增加,Svelte可以在当今令人惊叹的Frontend开发世界中脱颖而出,并成为公认的工具。


总结


尽管我不喜欢该框架的先前版本,但Svelte 3给我留下了很好的印象。 它简单,小巧,但可以做很多事情。 它与周围的一切都如此不同,它使我想起了当我从jQuery切换到Angular时所经历的兴奋。


无论您现在使用哪种框架,学习Svelte都可能仅花费几个小时。 一旦您学习了基础知识并理解了与写作习惯之间的差异,使用Svelte将会非常容易。


在俄语电报频道@sveltejs中,您一定会找到具有各种框架经验并准备分享有关Svelte的故事,思想和技巧的开发人员。

Source: https://habr.com/ru/post/zh-CN467091/


All Articles