svelte-logo-horizontal[1].svg

  • 2021-4-26 初探

从去年开始,就开始听说 svelte ,算得上三大框架之外的黑马,最大的亮点是没有运行时依赖,打包完全是原生js操作,没有虚拟dom,返璞归真,原汁原味普通js。

今天没啥事,来学一学。截至2021-4-26 版本 v3.37.0

叫什么 Svelte [svelt] 英语苗条的、修长的。
解决什么问题 js框架,三大框架之外的选项
有什么亮点 No Runtime,没有类库,没有虚拟dom,体积会小
有什么劣势 周边还不行
使用感受&推荐语 新东西尝鲜

官方说明

官方github
https://github.com/sveltejs/svelte

gitee国内源
https://gitee.com/mirrors/svelte

简单一看源码 src 下有两个文件夹:

  • compiler 显然是负责编译的,源码都是ts
    • compile parse preprocess utils
  • runtime 运行时
    • animate easing internal motion store transition

只看名称倒是还行,看得懂。

其他主要看文档。有个中文的 https://www.sveltejs.cn/tutorial/basics

基础使用

先不管三七二十一,快给我命令行,让我跑起来!

  1. # 创建项目
  2. npx degit sveltejs/template demo
  3. # 可选 把项目转成ts
  4. node scripts/setupTypescript.js
  5. # 安装依赖并运行
  6. yarn&&yarn dev

hey,还真跑起来了。基于 roolup 技术栈。

看一下 package.json

  • roolup 相关依赖
  • svelte 主库和相关check
  • ts相关

依赖挺简单的。

看一下 Playground 的一个demo

这是 app.svelte 的一个实例,提供变量供渲染:

  1. <script>
  2. let src = 'tutorial/image.gif';
  3. let name = 'Rick Astley';
  4. </script>
  5. <img {src} alt="{name} dances.">

这段代码会被编译成这样:

  1. /* App.svelte generated by Svelte v3.37.0 */
  2. import {
  3. SvelteComponent,
  4. attr,
  5. detach,
  6. element,
  7. init,
  8. insert,
  9. noop,
  10. safe_not_equal
  11. } from "svelte/internal";
  12. function create_fragment(ctx) {
  13. let img;
  14. let img_src_value;
  15. let img_alt_value;
  16. return {
  17. c() {
  18. img = element("img");
  19. if (img.src !== (img_src_value = src)) attr(img, "src", img_src_value);
  20. attr(img, "alt", img_alt_value = "" + (name + " dances."));
  21. },
  22. m(target, anchor) {
  23. insert(target, img, anchor);
  24. },
  25. p: noop,
  26. i: noop,
  27. o: noop,
  28. d(detaching) {
  29. if (detaching) detach(img);
  30. }
  31. };
  32. }
  33. let src = "tutorial/image.gif";
  34. let name = "Rick Astley";
  35. class App extends SvelteComponent {
  36. constructor(options) {
  37. super();
  38. init(this, options, null, create_fragment, safe_not_equal, {});
  39. }
  40. }
  41. export default App;
  • 引入了相关内置属性和方法
  • 填充 create_fragment 方法
  • 补充data
  • 书写 class

组件都不用导出,直接引入就行。组件名首字母大写。

语法介绍

下面介绍一些语法,拿 vue 语法进行比较。

  • v-html 使用 {@html string}p.innerHTML=ctx[0]
  • @click 使用 on:click={fn} 来书写,其他的事件 on: 开头
  • computed 可以使用 $: 标签语句,叫响应式声明。
  1. let count = 0;
  2. $:doubled = count * 2;
  3. // 逻辑
  4. $: console.log(`the count is ${count}`);
  5. // 一组逻辑
  6. $: {
  7. console.log(`the count is ${count}`);
  8. alert(`I SAID THE COUNT IS ${count}`);
  9. }

这不就是 label标签语法么,(label 标签语句 - MDN)前段时间 vue 想用 ref: a 时候被骂惨了不是:因为 Vue Ref 提案,我又刷了遍 label 语法
转眼就在这用上了?hhh

赋值能触发,但是数组操作不会更新。还是要赋值,两个办法,操作完重新赋值,或者不用 push操作直接用解构

  1. function addNumber() {
  2. numbers.push(numbers.length + 1);
  3. numbers = numbers;
  4. }
  5. function addNumber() {
  6. numbers = [...numbers, numbers.length + 1];
  7. }

有点意思。

原理先不看了。

语法继续:

  • 父组件传递 props 子组件如何接收? export let answer ,这里的 export 表示 prop 奇怪吧,语义改变了。
  • 如何实现 prop.defalut 呢?子组件 export let answer=1 ,这就是默认值了。
  • 父组件传递多个值可以使用 <Info {...obj} /> 的方式传递,子组件要不单独拆——写多个 export 接收,要不就 $$props 直接获取,官方说这样难以优化。细节先忽略
  • v-if 这里使用模板语法来实现,就普通模板语法,写起来感觉不是很方便,不如 v-if 好使

    1. {#if x > 10}
    2. <p>{x} is greater than 10</p>
    3. {:else if 5 > x}
    4. <p>{x} is less than 5</p>
    5. {:else}
    6. <p>{x} is between 5 and 10</p>
    7. {/if}
  • v-for 这里也是模板的 each 实现 {#each items as item, index}

    1. {#each cats as cat, i}
    2. <li><a target="_blank" href="https://www.youtube.com/watch?v={cat.id}">
    3. {i + 1}: {cat.name}
    4. </a></li>
    5. {/each}
  • 如何实现索引 key{#each items as item (item.id)} 最后追加一个 括号表示key

  • 如何通过异步请求更新页面,比如列表页?这里还是模板语法

仔细看案例:

  1. <script>
  2. let promise = getRandomNumber();
  3. async function getRandomNumber() {
  4. const res = await fetch(`tutorial/random-number`);
  5. const text = await res.text();
  6. if (res.ok) {
  7. return text;
  8. } else {
  9. throw new Error(text);
  10. }
  11. }
  12. function handleClick() {
  13. promise = getRandomNumber();
  14. }
  15. </script>
  16. <button on:click={handleClick}>
  17. generate random number
  18. </button>
  19. {#await promise}
  20. <p>...waiting</p>
  21. {:then number}
  22. <p>The number is {number}</p>
  23. {:catch error}
  24. <p style="color: red">{error.message}</p>
  25. {/await}

有点意思。页面一加载就执行了 promise 方法。pending展示第一部分,得到结果,赋值为 number 进行展示。其中第一个和第三个可以忽略。

  1. {#await promise then value}
  2. <p>the value is {value}</p>
  3. {/await}
  • 事件使用 on: 开头,也有一些修饰符,和vue不同,这里用的是 | ,多个也使用它来分割
    • preventDefault
    • stopPropagation
    • passive
    • capture
    • once
    • self
  1. <button on:click|once={handleClick}>
  2. Click me
  3. </button>
  • 子组件事件传递 emit ,这里 svelte 内置了,但看起来好麻烦。附件 on:message 就可以了

    1. import { createEventDispatcher } from 'svelte';
    2. const dispatch = createEventDispatcher();
    3. function sayHello() {
    4. dispatch('message', {
    5. text: 'Hello!'
    6. });
    7. }
  • 事件传递多层嵌套如何解决?首先可以在中间组件中使用 on:message 简写来实现转发 message 事件。这块说起来绕 https://www.sveltejs.cn/tutorial/event-forwarding

  • input里的 v-model 如何实现。 <input bind:value={name}> 其他的 bind:checked={yes}
  • :ref 处理dom <canvas bind:this={canvas}/> 页面先 let canvas ,当组件挂载了会自动改写 canvas 变量
  • 生命周期 onMountonDestroy ,这一点和 vue倒是差不多
  • 生命周期 beforeUpdateafterUpdate 函数
  • vue里的 nextTick 用的是 tick
  • 状态管理store,也就是vue里的vuex或者独立的eventBus 内置了 svelte/store/writable

    1. import { writable } from 'svelte/store';
    2. export const count = writable(0);

    此时,所有的组件都可以引入 count

  • count.set(0) 直接赋值,重置

  • count.update(n=>n+1) 通过高阶函数修改
  • 通过 count.subscribe(value=>{x=value}) 订阅变化,其他地方随便改,主动订阅来响应变化,并返回一个销毁函数
  • 需要主动销毁

这样很麻烦,忘了咋办。骚操作来了 <p>{$count}</p> 通过前缀表示自动订阅和销毁。

刚才看到 writable ,自然也有 readable 只读。 reable(value,()=>{}) ,有点绕,https://www.sveltejs.cn/tutorial/readable-stores

还有 store 里的 derived ,表示派生,似乎是 vue store里的 getter

vue里的transtion 也有映射 tweened , 内置 svelte/motion/tweened

先看到这。。https://www.sveltejs.cn/tutorial/tweened

后面我看了,都是进阶,慢慢看吧。

原理浅析

虚拟DOM是否高效?如何实现最小差异更新?
在字节发的文章中看到,区分 jsx和 template阵营。这里用到了 位掩码 bitMask 技术追踪脏值。
这个有点高级了。

位掩码是把多个布尔值存储在一个整数中的技术。比如 0000 1000 这一串数字,这可以表示 1 所在的位置是脏数据,0 表示没有变化。

这意味着 0000 1101 ,从右向左,第一位、第三位、第四位发生了变化。

这种表示方式最无脑,也最有效省事。但因为js数字长度限制,svelte使用数组来存放,二进制位数超了就增加长度。这个数组叫 component.$.dirty

有了这个数组我们就知道哪些数据发生了变化(理论上、概念上)。

不同于 vue react 的 VDOM diff,svelte记录了数据和dom节点的对应关系。

-1 参考链接

社区优秀文章

中文文档 https://www.sveltejs.cn/tutorial/basics