上一次看Svelte是2019年,已经是两年前了。

如果需要写一些前端程序,所以又调研了以下这些前端框架,发现基本上还是那些老的框架的天下,比如reactjs,vuejs,angularjs等等。

不过Svelte发展的也不错,在github/svelte上也已经有5万多关注了。作为Rich Harris的个人作品,实在是不易。

Rich Harris在The truth about Svelte这篇博文中阐述了他对Svelte的原旨的一些理解。很多框架(Elm、Imba、Idyll、Marko)采用的方式跟Svelte差不多,但是Svelte能成功,是因为它对传统基于JavaScript的前端作成方式具有较小的侵入,易于被大众接受。

Svelte不基于Virtual DOM,所以性能较好。另一款不基于Virtual DOM的JS框架是solidjs,在各种性能测评中超过了Svelte,如今也超过了1万关注了。这篇是solidjs作者2019年些的solidjs与svelte的对比:JavaScript UI Compilers: Comparing Svelte and Solid

尤大亲自评测 Vue3 和 Svelte(19个组件后Vue更好!)是一篇Vue和Svelte的对比,观点是组件多了的时候Svelte生成代码的尺寸缩放性可能有所不足。

Svelte学习

Tutorial

又把官方教程过了一遍。不得不说Svelte的文档工作做的很不错。

  • Svelte用于创建组件,一个组件通常含有脚本标签,样式标签,以及其他页面元素标签构成。方便起见,写组件的时候将这些混合在一个文件中。
    • 样式具有局部性,甚至不会应用于嵌套的子组件
  • 为了将脚本中定义的变量应用到页面部分,采用的是模板变量,也就是形如{name}的方式。模板变量可以用于标签的内容以及属性的值之上。
    • 属性值不一定需要使用引号括起来
    • 若变量与属性同名,则可以省略属性名和等号部分,例如<img {src}>
  • 粘贴html字符串需要使用{@html string},所以花括号并不是简单的变量替换,还可以指定一些开关
    • 还支持{count === 1 ? 'time' : 'times'},可能花括号里面的是一个表达式
  • 对属性名可以继续做文章,比如
    • on:click={incrementCount}挂载事件处理器
  • 反应性
    • $: doubled = count * 2,这里的美元符号用于告诉svelte这个表达式需要再count变化的时候重新执行
    • 除了单个表达式,$还支持花括号括起来的表达式块,以及if语句等等
    • 值得注意的是,$会延后执行所标记的表达式
    • svelte只会检测=号来判断数据是否跟新,这一点千万别忘了,+=之类的也算
    • 也可以触发对于标的辖属的反应式更i性能:obj.foo += 1, array[i] = x
    • 一条规则,const foo = obj.foo; foo.bar = 'baz';不能触发对obj.foo.bar的更新
  • props辖属
    • props用于声明组件的形式参数:export let answer;,实际参量通过标签的属性传递:<Nested answer={42}/>
      • 参数可以指定默认值
      • 参量可以从标的辖属中获取:<Info {...pkg}/>
  • DOM事件
    • 通过改写元素属性来描述事件的处理,比如:<div on:mousemove={handleMousemove}>
    • 也可以直接插入函数体,比如:on:mousemove="{e => m = { x: e.clientX, y: e.clientY }}
    • 可以加入额外的修饰符作为控制开关,比如:<button on:click|once={handleClick}>
    • 可以串连操作符,比如:on:click|once|capture={...}
    • 其他控制符
      • preventDefault
      • stopPropagation
      • passive (提高滚动性能,sevelte会自动添加)
      • nonpassive
      • capture 在下探阶段,而不是回弹阶段处理事件
      • once 让事件只触发一次
      • self 只在event.target是元素自身的时候触发
      • trusted 只在event.isTrusted为真的时候触发
    • 组件可以通过createEventDispatcher来派发事件
      • createEventDispatcher 必须在组件初始化的时候调用
    • 组件事件通过on:message={handleMessage}来处理,其中message为自定义的事件名
    • 组件事件不会回弹,需要显示上递,svelte提供了方便语法:<Inner on:message/>
    • 组件上递语法也可以用于DOM事件:<button on:click>
  • 映照(bindings),也就是将DOM中的值对照到svelte中,或者反之映射到DOM中
    • 简单的语法<input bind:value={name}>,用于将DOM中的值同步到svelte
    • 由于DOM中的值都是字符串,所以svelte提供了转化机制:<input type=number bind:value={a} min=0 max=10>
    • 对于复选框(checkbox),需绑定的是其checked属性:<input type=checkbox bind:checked={yes}>
    • 复选框如果指定了多个,需要绑定到一个数组:<input type=checkbox bind:group={flavours} name="flavours" value={flavour}>
    • 单选框(radiobox)只能多选一,可以将多个绑定到同一个对照:<input type=radio bind:group={scoops} name="scoops" value={1}>
    • 文本区域与文本输入框差不多:<textarea bind:value={value}>,可以简写为<textarea bind:value>
    • 单选列表的各项内容是对象,比如<select bind:value={selected} on:change="{() => answer = ''}">的selected是一个对象,可能只是使用其中的selected.value
    • 多选列表需要指定multiple,比如<select multiple bind:value={flavours}>
    • 也可以绑定可编辑内容,比如<div contenteditable="true" bind:innerHTML={html}></div>
    • 可以在{#each todos as todo}循环中对照到todo的某些辖属,注意这样会修改todos的内容
    • <audio>以及<video>等媒体元素可以有若干辖属可供绑定
    • 每个部块元素都有clientWidth, clientHeight, offsetWidth, offsetHeight四个辖属可以对照
    • 每个DOM元素或组件标的上都有this可以对照,但是只在组件标的的生命周期里有效
    • 组件定义的派入项(props)也可以用来对照
  • 存续周期(lifecycle)
    • 存续周期的各通告函数,在SSR时不会触发,且必须在组件初始化的时候设置
    • onMount在组件挂载到DOM时触发
      • onMount可以返回一个函数,在组件销毁时调用
      • onDestroy在组件销毁时调用
      • beforeUpdate和afterUpdate用于在DOM更新当前或者随后执行一些代码
      • tick可以将代码的执行延迟到下一个间隙(svelte是以microtask为间隔更新DOM的
  • store
    • 组件的状态是局部的,这时候就需要全局的状态协调,store就是干这事的
    • store提供只读或者读写的存储标的,一般定义在额外的js文件中,供其他组件引用。
    • 存储标的可以有set和update等操作,set接受的是值,update接受的是一个更新函数。
    • 可以订阅存储标的的更新,值得注意的是,订阅之后要注意退订,若组件消解的时候没有退订,则会导致内存泄漏
    • svelte提供了{$count}这种语法帮助自动订阅和退订
    • 存储标的之间还可以组合
    • 只要一个标的正确实现了subscribe,那么就是一个合格的存储标的,可以用于{$count}这种语法
    • 只要一个存储标的实现了set,就可以将其作为对照使用,比如<input bind:value={$name}>
    • 也可以直接对其进行赋值:<button on:click="{() => $name += '!'}">,相当于name.set($name + '!')
  • motion(行动特效)
    • tweened由svelte/motion提供,可以用来控制渐进性
    • svelte/easing则提供一些渐进性函数,比如cubicOut
    • spring则是由svelte/motion提供的另一个动作方式,可以对快速的变换做一些迟滞
  • transition(过场特效)
    • svelte/transition提供预设的过场函数,比如fade:<p transition:fade>Fades in and out</p>
    • 有的过场支持参数:<p transition:fly="{{ y: 200, duration: 2000 }}">
    • 入场和出场都可以设置不同的特效:<p in:fly="{{ y: 200, duration: 2000 }}" out:fade>
    • 可以自定义基于CSS以及JS的过场
    • 通过订阅on:introstart、on:outrostart、on:introend、on:outroend等事件可以探知过场的开始和结束
    • 默认情况下,过场会在任意的上级元素变更时触发,为了防止这一点,可以开启过场的局部性:<div transition:slide|local>
    • 过场可以推后执行,并与其他元素协调
    • 可以用{#key }模块来强制更新某元素子树,从而使之可以适配过场动画
  • Animations(动画特效)
  • 动作(action)
    • 动作是元素级别的代谢周期函数,
    • <div class="box" use:pannable ...>pannable动作挂载到此元素上
    • 动作可以接收额外的参数,比如<button use:longpress={duration}
  • 槽位(slot)
    • 如何让一个组件从外接收另一个组件作为其子元素
    • 办法是在组件内部预留槽位,可以预设默认值
    • 办法是某个组件的时候,必须在其包含区域指定要接收的其他组件
    • 如果想要多个槽位,必须给每个槽位命名,然后被接收的标的上必须指定目标槽位的名字
    • $$slots这个特殊变量用于提供传入的嵌套组件
    • 给槽位传派入值:<slot hovering={hovering}></slot>
    • 那嵌套组件如何知道有这个派入值存在呢,需要使用这种形式<Hoverable let:hovering={active}>来主动告知
  • 上下文(context)
    • 上下文和存储标的不同,前者只能用在某组件及其下属组件上,且不具有反应性;后者则可以用于全局,且有反应性
  • 特定元素
    • <svelte:self>可以让组件重复自身,但是使用时别忘了设置终止条件哦
    • <svelte:component>用于在运行时绘制组件
    • <svelte:window>用于在window范围侦听事件,用于破除组件的局部性
      • 还可以对照window的innerWidth、innerHeight、outerWidth、outerHeight、scrollX、scrollY,online (window.navigator.onLine)
      • 以上除了scrollX 和scrollY以外皆只读
    • <svelte:body>用于在body上侦听事件,用于破除组件的局部性
    • <svelte:head>用于在head上侦听事件,用于破除组件的局部性
    • <svelte:options>用于指定编译选项
      • immutable={true}可以简化编译器检查,默认不开
      • accessors={true}为组件的派入提供读写器,默认不开
      • namespace="..."组件的命名空间,使用时的常见值时"svg“
      • tag="..."将组件编译成web自定义元素时用的名字
    • <svelte:fragment>作为一个虚拟容器,用于组织落放到槽位的内容
  • 模块上下文(module context)
    • <script context="module">
      • 声明的代码只会在模块加载的时候运行一次
      • 其中export的是js意义上的export
      • 模块的默认导出是组件自身
  • 调试
    • 可以用{(console.log(user), '')}打印错误信息
    • 也可以用{@debug user}来激活调试器,@debug后面跟的是逗号分隔列表

examples

比教程里面的例子多一些。

其他

Web Component

javascript相关

老问题

其他框架

其他