SVELTE初识

152 阅读9分钟

SVELTE的介绍

什么是SVELTE

Svelte 是一个构建 web 应用程序的工具。Svelte 与诸如 React 和 Vue 等 JavaScript 框架类似,但是有一个关键的区别:Svelte 在 构建/编译阶段 将应用程序转换为理想的 JavaScript 应用,而不是在运行阶段解释应用程序的代码。这意味着不需要为框架所消耗的性能付出成本,并且在应用程序首次加载时没有额外损失。可以使用 Svelte 构建整个应用程序,也可以逐步将其融合到现有的代码中。还可以将组件作为独立的包(package)交付到任何地方。

核心思想:通过静态编译减少框架运行时的代码量。也就是说,vue 和 react 这类传统的框架,都必须引入运行时 (runtime) 代码,用于虚拟dom、diff 算法。Svelte完全溶入JavaScript,应用所有需要的运行时代码都包含在bundle.js里面了,除了引入这个组件本身,不需要再额外引入一个运行代码。

特性

No Runtime

Svelte通常被描述成“no-runtime”,这是因为Svelte在构建的过程中将大多数的工作转移到了编译阶段,而不是在浏览器中通过运行时代码进行处理。而传统的React 和 Vue都是基于运行时runtime的框架,组件在浏览器中运行时会产生额外的运行时开销。这些框架将组件抽象为虚拟 DOM(Virtual DOM)节点,并在运行时处理组件的创建、更新和销毁。这会导致一些额外的性能损失,尤其是在大型复杂应用程序中。

与之相反,Svelte 采用了一种不同的方法。在构建过程中,Svelte 将组件编译为高效的、可理解的 JavaScript 代码,而不需要额外的框架抽象层。结果是具有更少运行时开销和更快性能的应用程序

这里我们通过source-map-explorer可视化构建产物,通过它来分析打包之后的js文件以及source-map。

Svelte:(3.9kb)

其中,internal/index.mjs文件来自 Svelte 内部运行时代码,它包含 Svelte 运行时所需的核心功能,如组件创建、更新和销毁等。internal/index.mjs 通常在 Svelte 项目的构建产物中占用相当大的空间。这是正常的,因为它是 Svelte 应用程序运行所必需的基本运行时代码。这些代码包括组件的生命周期方法、数据更新和响应式逻辑实现等。

这里我们也能发现他是按需加载,没有使用的组件不会被打包到产物中。但在vue中即使具名插槽的组件没有使用也会存在在构建好的js中。

React: (141.65kb)

其中,react-dom-production.min.js负责管理 DOM 的操作。react提供了主要处理组件的创建、更新、销毁等虚拟 DOM 操作。scheduler 用于处理 React 中的任务调度。它负责优先级调度、任务分组和任务执行等。

通过使用 source-map-explorer 分析 React、Vue 和 Svelte 项目的构建产物,可以发现

  1. Svelte 项目的运行时代码通常比 React 和 Vue 更小。这是因为 Svelte 将大部分工作转移到编译阶段,从而减少了浏览器中运行时代码的体积。React 和 Vue 则依赖于更大的运行时库来处理组件生命周期、虚拟 DOM 等。
  2. 在 React 和 Vue 中可能会发现一些额外的库,如 react-dom(用于 React 的 DOM 操作)或 @vue/runtime-dom(用于 Vue 的运行时环境)。Svelte 则无需这些额外的库,因为它在编译时生成高效的 JavaScript 代码。
  3. 在 React 和 Vue 的构建产物中,会看到与虚拟 DOM 相关的模块和抽象层,这些模块用于处理组件的创建、更新和销毁。而在 Svelte 项目中,会发现更少的框架抽象层
Less Code

当我们使用svelte封装组件的时候,会发现svelte的代码量要远远小于Vue和React。这里我们以一个简单的例子来进行比较(在输入框中输入内容,通过按钮进弹框弹出)

React:

import React, { useState } from "react";

export default function App() {
  const [animal, setAnimal] = useState("dog");
  const showModal = () => {
    alert(`My favorite animal is ${animal}`);
  };

  return (
    <>
      <input
        type="text"
        value={animal}
        onChange={(e) => {
          setAnimal(e.target.value);
        }}
      />
      <button onClick={showModal}>弹出</button>
    </>
  );
}

Vue:

<template>
  <div>
    <input type="text" v-model="animal" />
    <button @click="showModal">弹出</button>
  </div>
</template>

<script>
export default {
  name: 'alertComponent',
  data () {
    return {
      animal: 'cat'
    }
  },
  methods: {
    showModal () {
      alert(`My favorite animal is ${this.animal}`);
    }
  }
}
</script>

<style></style>

Svelte

<script>
  let animal = 'dog';
  const showModal = () => {
    alert(`My favorite animal is ${animal}`);
  };
</script>
<input type="text" bind:value={animal} />
<button on:click={showModal}>弹出</button>
无虚拟DOM

Rich Harris 在设计 Svelte 的时候没有采用 Virtual DOM 是因为觉得Virtual DOM Diff 的过程是非常低效的。

虚拟 DOM 是一种有效的技术,用于最小化实际 DOM 操作,从而提高性能。然而,它也带来了一定的开销,因为它需要创建虚拟节点、比较差异并应用更新。之所以大家觉得虚拟DOM高效的理由是:在浏览器当中,JavaScript的运算在现代的引擎中非常快,但DOM本身是非常缓慢的东西而虚拟DOM不会直接操作原生的DOM节点。

但针对这些优势是有它固定的业务场景的,针对一些大型的系统,使用虚拟DOM 确实可以大大减少开发难度节省性能,但针对小型系统本身现有的机能已经足够满足性能开销,直接操作 DOM 也未必会与使用虚拟DOM 有显著的差异,反而是虚拟DOM 需要的 diff/patch 算法会更显的冗余。

下面我们可以通过一个长表格的案例来体验以下VDOM Diff和SVELTE的性能。

SVELTE:

<script>
  function generateData() {
    let arr = [];
    for (let iterator = 0; iterator < 100000; iterator++) {
      arr.push({ title: "title" + iterator, id: iterator });
    }
    return arr;
}
  let data = generateData();
  function updateData() {
    data = data.map((item, index) => {
      if (index % 10 === 0) {
        return { ...item, title: "Updated " + item.title };
      }
      return item;
    });
  }
</script>
<div>
  <button on:click={updateData}>Update Data</button>
</div>
<ul>
  {#each data as item}
    <li>{item.title}</li>
  {/each}
</ul>

React:


function App() {
  const [data, setData] = useState(generateData());
  function generateData() {
    let arr = [];
    for (let iterator = 0; iterator < 100000; iterator++) {
      arr.push({ title: "title" + iterator, id: iterator });
    }
    return arr;
  }
  const updateData = () => {
    const newData = data.map((item, index) => {
      if (index % 10 === 0) {
        return { ...item, title: "Updated " + item.title };
      }
      return item;
    });
    setData(newData);
  };

  return (
    <div className="App">
      <div>
        <button onClick={updateData}>Update Data</button>
      </div>
      <ul>
        {data.map((item) => (
          <li key={item.id}>{item.title}</li>
        ))}
      </ul>
    </div>
  );
}

通过对比我们可以发现相对来说svelte更加流畅。进一步证明在有些场景下React使用虚拟DOM技术,在运行时对组件进行更新和重新渲染,可能会导致更多的性能开销。

svelte的出现

2016年,Svelte发布,Svelte是一种新型的Javascript渐进式框架,通过静态编译减少框架运行时的代码量,可以在编译时让前端应用程序更快、更小和更可维护。一个 Svelte 组件编译之后,所有需要的运行时代码都包含在里面了,除了引入这个组件本身,你不需要再额外引入一个所谓的框架运行时!

解决的问题: - no virtual DOM - truly reactive

svelte和其他前端框架响应式原理的比较
  • 响应式原理

    • React

      • React 开发者使用 JSX 语法来编写代码,JSX 会被编译成 ReactElement,运行时生成抽象的 Virtual DOM。

      • 然后在每次重新 render 时,React 会重新对比前后两次 Virtual DOM,如果不需要更新则不作任何处理;如果只是 HTML 属性变更,那反映到 DOM 节点上就是调用该节点的 setAttribute 方法;如果是 DOM 类型变更、key 变了或者是在新的 Virtual DOM 中找不到,则会执行相应的删除/新增 DOM 操作。

      • 我们看看一个简单的点击计数的 DEMO 背后 React 都做了哪些事情。

        import React from "react";
        
        const Counter = () => {
          const [count, setCount] = React.useState(0);
        
          return <button onClick={() => setCount((val) => val + 1)}>{count}</button>;
        };
        
        
        function App() {
          return <Counter />;
        }
        
        export default App;
        

        大致可以将整个流程分为三个部分,首先是调度器,这里主要是为了处理优先级(用户点击事件属于高优先级)和合成事件。 第二个部分是 Render 阶段,这里主要是遍历节点,找到需要更新的 Fiber Node,执行 Diff 算法计算需要执行那种类型的操作,打上 effectTag,生成一条带有 effectTag 的 Fiber Node 链表。常说的异步可中断也是发生在这个阶段。

        第三个阶段是 Commit,这一步要做的事情是遍历第二步生成的链表,依次执行对应的操作(是新增,还是删除,还是修改...)

        前置操作完成,计算出原来是 nodeValue 需要更新,最终执行了 firstChild.nodeValue = text

    • Vue

      发布者订阅模式, 大致过程是编译过程中收集依赖,基于 Proxy(3.x) ,defineProperty(2.x) 的 getter,setter 实现在数据变更时通知 Watcher。Vue 的实现很酷,每次修改 data 上的数据都像在施魔法。

    image.png

基于SVELTE特性设计响应式编程

脱离VirtuaDom实现响应式编程

需求描述:使用js生成一个button,button里面绑定了一个count属性,点击一次count+1,button的内容页发生相应变化

function reactiveButton() {
  let count = 0;
  const button = document.createElement('button');
  function renderButton() {
      button.textContent = `count is ${count}`;
  }

  function setCount() {
      count ++;
      renderButton();
  }
  
  button.onclick = setCount;

  renderButton();
  
  return button
}

const button = reactiveButton();
document.body.appendChild(button);
操作颗粒度更细分

上面代码的操作颗粒度是按钮,举一个极端例子,按钮的内容是前面一万多个字符,然后在跟着一个count的值,当变更count并且执行renderButton的时候,会对整个按钮的文案进行操作。但是如果我们把count改变操作对象从button改为textNode,那就轻松多了

function reactiveButton() {
  let count = 0;
  const button = document.createElement('button');
  // 为count和之前的文本各自创建TextNode元素
  const t1 = document.createTextNode('count is ');
  const t2 = document.createTextNode(count);
  button.appendChild(t1);
  button.appendChild(t2);
  // 颗粒度改为直接操作count直接影响的TextNode元素
  function renderTextNode() {
      t2.textContent = count;
  }

  function setCount() {
      count ++;
      renderTextNode();
  }
  
  button.onclick = setCount;
  return button
}
分离dom操作和变量赋值
// 数据的定义的操作
function instance() {
    let count = 0;
    function setCount() {
      count ++;
    }
    return [count, setCount]
}


// 创建组件实例,返回dom的操作封装
function create_fragment(target = document.body, ctx) {
    let button
    let t1;
    let t2;
    let mounted = false;
    return {
        // 元素的初始化
        create() {
            button = document.createElement('button');
            t1 = document.createTextNode('count is ');
            t2 = document.createTextNode(ctx[0]);
        },
        // 元素的插入操作
        // target表示父元素,anchor表示锚点元素
        // 如果anchor存在,则插入到anchor之前,如果不存在,则作为父元素的最后一个子元素
        mount(target, anchor) {
            button.appendChild(t1);
            button.appendChild(t2);
            target.insertBefore(button, anchor || null);
            
            if (!mounted) {
              mounted = true;
              button.onclick = () => {
                  // 点击后触发上下文对象中暴露的函数
                  ctx[1].call();
                  this.update();
              }
            }
        },
        
        // 更新节点,因为只有t2的文本节点需要更新,且收到上下文对象中的count影响
        update() {
            t2.textContent = ctx[0]
        }
    }
}

// 分别初始化dom和定义影响dom的变量
const ctx = instance();
const b = create_fragment(document.body, ctx);
b.create();
b.mount(document.body, null);

通过上诉的方式,确实页面确实显示出了一个按钮,且按钮内容为预期中的count is 0,但是点击后内容没有发生变化,原因是返回的ctx[1]函数改的是函数内作用域的count,并不是暴露出去的ctx[0]

维护一个上下文数组和改变内容的方法
function instance(
+    $$invalidate // 初始化的时候传入一个可以有效改变上下文数组的函数
) {
  let count = 0;
  const setCount = () => {
-    count ++;
+    $$invalidate(0, count ++)
  }

  return [count, setCount]
}


// 维护一个上下文数组和改变上下文对象的方法
- const ctx = instance();
+ const $$ = {};
+ $$.ctx = instance((i, val) => {
+  $$.ctx[i] = val;
+})
const b = create_fragment(
-    ctx
+    $$.ctx
);

这样处理后确实点击内容会发生变化,但是很奇怪,点击第一次的时候,没有发生变化.

  • 原因?

    count ++这类的语法,是会先返回count,然后再进行+1操作,例如:

let a = 1;
const b = a ++;
// 这时候打印的还是1
console.log(b)
  • 解决方案

    不可能去改count++的常用写法,添加传参让用户传入最新的count值

function instance($$invalidate) {
  let count = 0;
  const setCount = () => {
    $$invalidate(
        0,
        count ++,
+       count
    )
  }

  return [count, setCount]
}


const $$ = {};
$$.ctx = instance((
    i,
    val,
+   ...res
) => {
-  $$.ctx[i] = val
+  $$.ctx[i] = res.length ? res[0] : val;
})
多个变量

修改需求:页面上添加一个文本内容,显示count是否超过3了

简单的想法:对外抛出一个isMoreThan3变量,setCount方法执行的时候同时去改countisMoreThan3的值

function instance($$invalidate) {
  let count = 0;
+ let isMoreThan3 = count > 3;
  const setCount = () => {
    $$invalidate(0, count ++, count)
+   $$invalidate(2, count > 3)
  }

  return [
      count,
      setCount,
+     isMoreThan3,
  ]
}

同时进行之前类似的元素封装处理:create方法添加两个文本节点,mount方法插入两个文本节点,update方法改变绑定isMoreThan3的文本节点

// 创建节点
function create() {
    button = document.createElement('button');
    t1 = document.createTextNode('count is ');
    t2 = document.createTextNode(ctx[0]);
+   t3 = document.createTextNode(' is more than 3: ');
+   t4 = document.createTextNode(ctx[2])
}

// 插入节点
function mount(target, anchor) {
    button.appendChild(t1);
    button.appendChild(t2);
+   button.appendChild(t3);
+   button.appendChild(t4);
    target.insertBefore(button, anchor || null);

    if (!mounted) {
      mounted = true;
      button.onclick = () => {
        ctx[1].call();
        this.update();
      }
    }
}

// 更新节点
function update() {
    t2.textContent = ctx[0]
+   t4.textContent = ctx[2]
}

更新渲染原理

模拟了svelte实现响应式的过程,Svelte本身 是如何更新渲染结果的呢?Svelte 整体的更新渲染流程是怎么样子的呢?我们来编译两个例子。

Demo1

我们先看看最简单的代码会被编译成什么样子。

<script>
  let name = 'world';
</script>

<h1>Hello {name}!</h1>
/* App.svelte generated by Svelte v3.59.2 */
function create_fragment(ctx) {
  let h1;

  return {
    c() {
      h1 = element("h1");
      h1.textContent = `Hello ${name}!`;
    },
    m(target, anchor) {
      insert(target, h1, anchor);
    },
    p: noop,
    i: noop,
    o: noop,
    d(detaching) {
      if (detaching) detach(h1);
    }
  };
}

let name = 'world';

class App extends SvelteComponent {
  constructor(options) {
    super();
    init(this, options, null, create_fragment, safe_not_equal, {});
  }
}

export default App;

这段代码经由编译器编译后产生如下代码,包括三部分:

  • create_fragment方法,这个函数会返回一个对象,包含组件对应的多个方法:

    • c:代表create,用于根据模版内容,创建对应DOM Element。
    • m:代表mount,用于将c创建的DOM Element插入页面,完成组件首次渲染。
    • d:代表detach,用于将组件对应DOM Element从页面中移除,detach方法会调用parentNode.removeChild.
  • class App的声明语句,每个组件对应一个继承自SvelteComponent的class,实例化时会调用init方法完成组件初始化,create_fragment会在init中调用。

  • 变量name的声明

仔细观察流程图,会发现App组件编译的产物没有图中fragment内的p方法,由于上面的代码中是个静态的字符串,所以p对应的值为noopno operate没有操作。

Demo2

这是我们再来看一下,可以改变状态的Demo,例子如下:

<div>
    {count}
     <button on:click={onClick}>click</button>
</div>
<script>
  let count= 1
  function onClick() {
    count= count+ 1
  }
</script>

这个时候,我们会发现编译产物发生了变化。

/* App.svelte generated by Svelte v3.59.2 */

function create_fragment(ctx) {
  let div;
  let t0;
  let t1;
  let button;
  let mounted;
  let dispose;

  return {
    c() {
      div = element("div");
      t0 = text(/*count*/ ctx[0]);
      t1 = space();
      button = element("button");
      button.textContent = "click";
    },
    m(target, anchor) {
      insert(target, div, anchor);
      append(div, t0);
      append(div, t1);
      append(div, button);

      if (!mounted) {
        dispose = listen(button, "click", /*onClick*/ ctx[1]);
        mounted = true;
      }
    },
    p(ctx, [dirty]) {
      if (dirty & /*count*/ 1) set_data(t0, /*count*/ ctx[0]);
    },
    i: noop,
    o: noop,
    d(detaching) {
      if (detaching) detach(div);
      mounted = false;
      dispose();
    }
  };
}

function instance($$self, $$props, $$invalidate) {
  let count = 1;

  function onClick() {
    $$invalidate(0, count = count + 1);
  }

  return [count, onClick];
}

class App extends SvelteComponent {
  constructor(options) {
    super();
    init(this, options, instance, create_fragment, safe_not_equal, {});
  }
}

export default App;
  1. 顶层声明变量的方式变成了instance方法。

    **Svelte编译器会追踪<script>**内所有变量声明,一旦存在改变该变量的语句如count++,赋值等操作,就会将变量提取到instance中,并返回组件对应的ctx;

  2. 同时如果改变的变量操作语句可以通过模板被引用,则该语句就会被$$invalidate包裹。用于后续的DOM更新。

  3. create_fragment生成p方法来更新对应的 DOM 元素。Svelte 是在编译时候,就记录了数据 和 DOM 节点之间的对应关系,并且保存在 p 函数中。p函数set_data 就是封装了 DOM 的原生方法(比如说 innerHtml),把 DOM 节点更新。

上面我们了解了编译产物的变化,那如果此时我们点击按钮,以上这些变化时如何实现DOM更新的呢?它的详细流程是怎样的?

我们知道Svelte 回归到原生的JS,每个组件对应一个JS类,即组件实例instance。当组件的状态发生变化的时候,生成一个新的组件实例,使用差异算法来比较新旧组件的实例DOM的结构,然后更新需要更新的部分。具体流程如下:

  • 首先,我们在浏览器执行如上所示的Svelte 编译修改之后的代码。因此当click事件发生之后,增加的instance方法将onClick的方法改写,会对count进行重新赋值(count = count+1),随后调用 ?invalidate方法

  • 随后,?invalidate 方法又调用了 make_dirty 方法,make_dirty 是记住已经脏的数据。Svelte使用 位掩码(bitMask) 的技术来跟踪哪些值是脏的。位掩码是一种将多个布尔值存储在单个整数中的技术,一个比特位存放一个数据是否变化,一般**1表示脏数据, 0 表示是干净数据。简单点说,就是二进制 0000 0001代表第一个值是脏数据,0000 0011代表第一第二位都是脏数据。Svelte采用数组的方式来存放。数组中一项是二进制31 位的比特位。假如超出31 个数据了,超出的部分放到数组中的下一项 。这个记录的数组就是 component.?.dirty **数组。

  • 有了脏数据之后,会调用schedule_update() 函数,schedule_update 的作用是,把一个 flush 的方法放到一个 Promise.then 里面,让 flush 方法在这一帧的微任务被执行的时候执行。就会在一帧16ms的微任务调用更新DOM节点的方法。

  • flush 方法里面的逻辑是:遍历所有的diry_components 中的组件,调用update 方法,update 方法里面,最后调用了组件的 p() 方法。
和我们设计的响应式编程的区别
  • 封装了dom的创建函数:
- document.createElement('button')
+ element('button)
  • 封装了容器代码
-const $$ = {};
-  $$.update = () => {};
-  $$.dirty = 0;
-  $$.ctx = instance($$, (i, ret, ...res) => {
-    const oldVal = $$.ctx[i];
-    $$.ctx[i] = res.length ? res[0] : ret;
-    if (oldVal !== $$.ctx[i]) {
-      console.log($$.dirty, i, 'before')
-      $$.dirty |= 1 << (i % 31)
-      console.log($$.dirty, 'after')
-    }
-    update($$);
-  })
-  $$.update();
-  const c = create_fragment($$.ctx);
-  $$.fragment = c;
-  c.create();
- c.mount(props.target, props.anchor);
- $$.dirty = 0;
+ init(this, options, instance, create_fragment, safe_not_equal, {});
  • 提供了卸载fragment的方法
+ d(detaching) {
+    if (detaching) detach(button);
+    mounted = false; dispose(); 
+ }

基本使用

生命周期

svelte的声明周期函数相对比较简单,主要关注组件挂载、更新和卸载时的操作。

  • onMount:组件挂载到 DOM 时调用,建议放异步请求,方便做服务端渲染
  • beforeUpdate:组件状态更新之前调用。
  • afterUpdate:组件状态更新完毕并重新渲染后调用。
  • onDestroy:组件卸载时调用,比如说订阅的全局 store, 定时器等
  import { onMount, beforeUpdate, afterUpdate, onDestroy } from 'svelte';

  onMount(() => {
    console.log('组件挂载到 DOM');
  });

  beforeUpdate(() => {
    console.log('组件数据更新之前');
  });

  afterUpdate(() => {
    console.log('组件数据更新之后');
  });

  onDestroy(() => {
    console.log('组件卸载');
  });

react和vue的生命周期更复杂,这里我们就不详细讲解了。

Slot

在 Svelte 中,插槽使用名为 <slot> 的特殊元素表示。如果子组件中有一个 <slot> 元素,那么父组件可以传递内容到该位置。

<DefaultSlot>
  <!-- put content here -->
  <span>111</span>
  <span>222</span>
</DefaultSlot>
<NameSlot>
  <span slot="name">
    名字
  </span>
  <span slot="address">
    地址CCCC
  </span>
</NameSlot>


<slot name="name">
  <span>Unknown name</span>
</slot>

<slot name="address">
  <span >Unknown address</span>
</slot>

在 React 中,插槽通常使用 props.children 来实现。子组件可以访问 props.children 以获得父组件传递的内容。

function Card(props) {
  return <div className="card">{props.children}</div>;
}

在vue中,slot是一种在父组件中向子组件传递内容的方式。插槽允许开发人员自定义子组件内部的结构。插槽也分为两种类型:默认插槽和具名插槽。

<template>
  <div class="card">
    <slot></slot>
    <slot name="name"></slot>
   <slot name="address"></slot> 
  </div>
</template>

 <NameSlot>
  <template v-slot:name>
    <h2>这是名字</h2>
  </template>
  <template v-slot:address>
    <p>这是地址</p>
  </template>
</NameSlot>

Store

在 Svelte 中,Store(存储)是一种用于在组件之间共享响应式状态的机制。Svelte 提供了一个名为 writable 的方法来创建可读写的存储,以及一个名为 readable 的方法来创建只读存储,还提供了一个方法用于创建一个基于其他存储的派生存储。

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

// 获取
import { count } from './stores.js';
let count_value;
const unsubscribe = count.subscribe(value => {
  count_value = value;
});
<h1>The count is {count_value}</h1>
<Incrementer/>
<Decrementer/>


// 更改
<script>
  import { count } from './stores.js';

  function decrement() {
    count.update(n => n - 1);
  }
</script>

<button on:click={decrement}>
  -
</button>

<script>
  import { count } from './stores.js';

  function increment() {
    count.update(n => n + 1);
  }
</script>

<button on:click={increment}>
  +
</button>

在Vue中我们一般使用Vuex来实现store,通过state获取数据,dispatch改变数据,这里我们就不详细介绍了,同样的,react中我们可以使用redux来实现store

<!-- src/components/Counter.vue -->
<template>
  <div>
    <button @click="decrement">-</button>
    <span>{{ count }}</span>
    <button @click="increment">+</button>
  </div>
</template>

<script>
export default {
  computed: {
    count() {
      return this.$store.state.count;
    }
  },
  methods: {
    increment() {
      this.$store.dispatch('increment');
    },
    decrement() {
      this.$store.dispatch('decrement');
    }
  }
};
</script>

动画

Svelte 提供了内置的动画库,可以帮助轻松地为组件创建丰富的动画。例如我们实现一个进度条的组件

<script>
  import { writable } from 'svelte/store';
  const progress = writable(0);
</script>

<style>
  progress { display: block; width: 100%; }
</style>

<progress value={$progress}></progress>

{#each [0, 0.25, 0.5, 0.75, 1] as p}
  <button on:click={() => $progress = p}>
    {p*100}%
  </button>
{/each}

tweened(补间动画)

这个时候我们会感觉它比较生硬,缺乏动画效果。让我们修改progressstore 为tweened

import { tweened } from 'svelte/motion';
  const progress = tweened(0, {
    duration: 400
  });
transition(过渡效果)

将进入元素或离开元素的过程如能优雅地使用过渡效果,可以大大增加用户界面的吸引力。Svelte 使用transition指令让这种操作轻而易举。下面我们已fade为例,此外还有fly,in,等其他方法。

<script>
  import { fade } from 'svelte/transition';
  let visible = true;
</script>

<label><input type="checkbox" bind:checked={visible}>显示</label>

{#if visible}
  <p transition:fade>淡入淡出效果展示</p>
{/if}