Garlic Garlic

Svelte从入门到精通——生态

发表于 阅读时长7分钟

尽管 Svelte 的生态环境和 React、Vue 这两大框架相比稍显得不够庞大,但对于基本的开发已经覆盖全面。为了展现其全面性,会继续从三大框架的各个方面进行对比。

对比

SvelteReactVue
cli旧版:svelte-cli
目前:Vite
create-react-appVue Cli
Vite
路由routify
svelte-spa-router
svelte-routing
react-routervue-router
状态管理svelte/storereduxvuex
pinia
SSRSvelteKitNext.jsNuxt
静态/文档SvelteKit
Elder.js
Next.js
Gatsby
Docusaurus
VuePress
VitePress
UI 组件shadcn-svelte
svelte-material-ui
Flowbite Svelte
Melt UI
Ant Design
MUI
chakra
Element Plus
Ant Design Vue
Vant
测试svelte-testing-libraryreact-testing-library
Enzyme
vue-testing-library
Vue Test Utils
Vitest
dev toolsSvelte DevToolsReact Developer ToolsVue.js devtools
webpack loadersvelte-loaderbabel-loadervue-loader
Rollup pluginrollup-plugin-svelte@rollup/plugin-babelrollup-plugin-vue
Vite plugin@sveltejs/vite-plugin-svelte@vitejs/plugin-react@vitejs/plugin-vue
NativeSvelte NativeReact NativeWeex
小程序tarojs-plugin-svelteTarompvue
社区sveltesocietyreact communityvue community

Svelte 5

Svelte 宣布了一项新特性 "runes",这个特性将在 Svelte5 中大幅改变开发者的体验。Rune 是一种可以影响 Svelte 编译器的符号,Svelt5 中的 runes 使用函数语法实现,达到和现有的声明式语法等效的效果,如 $state 用于声明响应式状态。Rune 的引入,可以让你的 .svelte 文件之外的其他文件也拥有响应式能力。相较于现有的 store APIrune 可以更简单地处理更复杂的事物。

Runes

Svelte 5 最大的改动便是引入了Runes。Runes 是一组函数式的符号,无需额外引入,可以直接使用,是 Svelte5 语言的特性,目前有以下 Runes,文章里会挑选比较重要的几个进行讲解:

  • $state
  • $derived
  • $effect
  • $props

$state

<script>
	let count = $state(0);
</script>

<button on:click={() => count++}>
click
</button>
{count}

对比之前的数据声明,多了$state

使用$state声明的数组可以直接操作了。

<script>
  const arr = $state([1, 2]);

  const onAdd = () => {
    arr.push(1);
  };

  const onSub = () => {
    arr.pop();
  };
</script>

<p>
  <button on:click="{onAdd}">增加</button>
  <button on:click="{onSub}">减少</button>
</p>
数组:{arr.join(',')}

$derived

$derived接收一个参数,这个参数是一个没有副作用的表达式。

<script>
  let count = $state(0);
  let double = $derived(count * 2);

  const onClick = () => {
    count++;
  };
</script>

<button on:click="{onClick}">更新</button><br />
count: {count} double: {double}

我们可以传count * 2,但是不能传count++。 在 Svelte4 中,我们要声明一个派生属性,需在$: 里进行。

$effect

runs when the component is mounted, and again whenever count or doubled change,after the DOM has been updated.

因此,$effect相当于$: {}onMountafterUpdate的结合体。笔者对此改动表示热烈欢迎,因为本人始终觉得在有些框架中,一个组件对外提供一大串又丑又长的生命周期,着实加大了开发者的心智负担。

<script>
  let width = $state(10);

  $effect(() => {
    console.log("width改变", width);
  });
</script>

width: <input type="number" bind:value="{width}" />

$props

和明显,用来接收 props 的 Runes。

<script>
  export let value;
</script>

子组件:{value}
<script>
  let { value } = $props();
</script>

子组件:{value}

Snippets

俗称片段。使用 Snippets 可以进行内容复用。

<script>
  let arr = $state([
    {
      name: "carter",
      age: 18,
      gender: "男",
    },
    {
      name: "lily",
      age: 19,
      gender: "女",
    },
  ]);
</script>

{#snippet person({ name, age, gender })}
<p>
  <span>姓名:{name}</span>
  <span> 年龄:{age}</span>
  <span>性别:{gender}</span>
</p>
{/snippet} {#each arr as item, i} {@render person(item)} {/each}

使用{#snippet snippetName()}...{/snippet}来定义我们要复用的片段,使用{@render snippetName()}来复用定义好的片段。

在之前,如果我们要复用这一段代码,只能把它放入到另一个 svelte 文件中,当成组件来引用。

事件

事件监听

在演示 Runes 和 Snippets 时,笔者在使用到数据绑定时,仍旧使用的是 Svelte4on:eventname的形式。其实在 Svelte5 中,关于方法的使用也有更新:从原来on:eventname的形式转变为oneventname的形式。

<script>
  const onClick = () => {
    console.log('click');
  }
</script>

- <button on:click={onClick}>click</button>
+ <button onclick={onClick}>click</button>

组件事件

使用$props()来接收方法。终于不用使用难用的createEventDispatcher了。

<script>
  let { onClick, onClick2 } = $props();
</script>

<button onclick={onClick}>click</button>
<button onclick={e => onClick2('hello svelte')}>click2</button>
<script>
  import Svelte5 from "./Svelte5.svelte";

  const onClick = (event) => {
    console.log("event", event);
  };

  const onClick2 = (value) => {
    console.log("value", value);
  };
</script>

<Svelte5 {onClick} {onClick2} />

除了接收方法,我们还能接收插槽内容。没错,在 Svelte5 中,插槽的使用转而投向了 jsx 的写法,通过let { children } = $props()来接收插槽内容。

<script>
  let { children } = $props();
</script>

<div>
  <header>头部</header>
  <main>{@render children() }</main>
  <footer>底部</footer>
</div>
<script>
  import Svelte5 from "./Svelte5.svelte";
</script>

<Svelte5> 内容 </Svelte5>

这里的{@render ...}和后面介绍的 Snippets 有关。思考:如何接收具名插槽?

方法

untrack

<script>
  let width = $state(10);
  let height = $state(10);
  let area;

  $effect(() => {
    console.log("width or height change", width, height);
  });
</script>

width: <input type="number" bind:value="{width}" />, height:
<input type="number" bind:value="{height}" />

如果我们只想在 width 执行时输出 console,那就需要不追踪 height 的依赖。

<script>
  import { untrack } from "svelte";

  let width = $state(10);
  let height = $state(10);

  $effect(() => {
    console.log(
      "width or height change",
      width,
      untrack(() => height)
    );
  });
</script>

width: <input type="number" bind:value="{width}" />, height:
<input type="number" bind:value="{height}" />

mount

import { mount } from "svelte";
import App from "./App.svelte";

const app = mount(App, {
  target: document.querySelector("#app"),
  props: { some: "property" },
});

Svelte4

import App from "./App.svelte";

const app = new App({
  target: document.getElementById("app"),
});

export default app;

更多详细内容参考Svelte5 抢先看

小结

本章我们学习了:

  • Svelte 对比其他框架的生态优劣
  • Svelte5 的新特性前瞻