Svelte5 抢先看!

Svelte5 抢先看!

安装

bash 复制代码
npm create svelte@latest svelte-5

当我们在执行上述命令后,首先会看到如下提示:

css 复制代码
┌  Welcome to SvelteKit!
│
◆  Which Svelte app template?
│  ● SvelteKit demo app (A demo app showcasing some of
the features of SvelteKit - play a word guessing game
that works without JavaScript!)
│  ○ Skeleton project
│  ○ Library project
└

从弹出的选项中可以看到,这里给我们提供了体验Svelte 5的选项。

接着是正常的安装依赖操作。

bash 复制代码
cd svelte-5
npm install 
npm run dev

安装完后,我们可以看到package.json里的依赖显示:

json 复制代码
{
  "svelte": "^5.0.0-next.1",
}

当然,如果不想执行上述操作也想体验Svelte 5,那可以尝试官方提供的REPL

Runes

Svelte 5最大的改动便是引入了Runes

也许大家对Runes不太熟悉,但如果说到英雄联盟里的瑞兹,相信大家耳熟能详。瑞兹的英文全称是THE RUNE MAGE ,中文翻译是符文法师。不错,Runes即符文。

为了读起来更自然点,文章中将继续以Runes来说明。Runes是一组函数式的符号,无需额外引入,可以直接使用,是Svelte5语言的特性,目前有以下Rune:

  • $state
  • $state.frozen
  • $derived
  • $derived.by
  • $effect
  • $effect.pre
  • $effect.active
  • $effect.root
  • $props
  • $inspect

$state

html 复制代码
<script>
    let count = $state(0);
</script>

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

在class中也能使用

html 复制代码
<script>
class Person {
    name = $state();

    constructor(name) {
        this.name = name;
    }
}

const person = new Person();
</script>

<input bind:value={person.name} /><br />
姓名:{person.name}

对比之前的数据声明,多了$state。但使用了$state声明的数组和对象,不再像Svelte4那样,需要使用一些小技巧来赋值更新。

在Svelte 5中,数组的更新就和在js操作中一般。

html 复制代码
<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(',')}

对象演示:

html 复制代码
<script>
const obj = $state({
    foo: {
        bar: 'hello'
    }
});

</script>

<input bind:value={obj.foo.bar} />
{obj.foo.bar}

$state.frozen

html 复制代码
<script>
let arr = $state.frozen([1,2]);

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

const update2 = () => {
    arr = [...arr, 1];
}
</script>

<p>
    <button on:click={update1}>更新1</button>
    <button on:click={update2}>更新2</button>
</p>
数组:{arr.join(',')}

使用了$state.frozen声明后的数组,无法直接操作数组。vscode会给出提示:

在浏览器中点击第一个更新,也会报错:

当我们调用update2时,数组能正常更新。

看下官网的一个提示:

Objects and arrays passed to $state.frozen will be shallowly frozen using Object.freeze(). If you don't want this, pass in a clone of the object or array instead.

html 复制代码
<script>
let arr = $state.frozen([{
        obj: {
                name: 'bob',
                age: 18
        }
}]);
let arr2 = $state.frozen([{name: 'carter', age: 19}]);

let arr3 = $state.frozen(['david']);

arr[0].obj.name = 'svelte';
arr2[0].name = 'svelte';
arr3[0] = 'svelte';
</script>

{JSON.stringify(arr)}
{JSON.stringify(arr2)}
{JSON.stringify(arr3)}

在上面这个例子中,arr3[0] = 'svelte';会直接报错,剩下两个赋值则不会。把报错赋值的语句去掉,我们可以看到:

$derived

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

html 复制代码
<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中,我们要声明一个派生属性,需在$: 里进行。

$derived.by

$derived.by接收一个函数。

html 复制代码
<script>
  let arr = $state([1,2,3]);
  let total = $derived.by(() => {
    return arr.reduce((pre, cur) => pre + cur, 0);
  });

  const onAdd = () => {
    arr.push(arr.length + 1);
  }
</script>

<button on:click={onAdd}>add</button>
total: {total}

$effect

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

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

在Svelte 4中:

html 复制代码
<script>
  import { afterUpdate } from 'svelte';
  let width = 10;
  let height = 10;

  $: console.log('width改变', width);

  afterUpdate(() => {
    console.log('afterUpdate')
  });
</script>

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

在Svelte 5中,使用$effectrune:

html 复制代码
<script>
  let width = $state(10);

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

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

$effect.pre

用于替代beforeUpdate生命周期。

我们看个Svelte 4的例子:

html 复制代码
<script>
  import { beforeUpdate, afterUpdate } from 'svelte';
  let width = 10;

  beforeUpdate(() => {
    const dom = document.querySelector('#width');
    if (dom) {
      console.log('beforeUpdate', dom.innerHTML);
    }
  });

  afterUpdate(() => {
    const dom = document.querySelector('#width');
    if (dom) {
      console.log('afterUpdate', dom.innerHTML);
    }
  });
</script>

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

在Svelte 5中:

html 复制代码
<script>
  let width = $state(10);

  $effect.pre(() => {
    const dom = document.querySelector('#width');
    if (dom) {
      console.log('svelte5 beforeUpdate', dom.innerHTML, width);
    }
  })

  $effect(() => {
    const dom = document.querySelector('#width');
    if (dom) {
      console.log('svelte5 afterUpdate', dom.innerHTML, width);
    }
  });
</script>

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

在这里之所以要把width也一起打印出来,是因为$effect$effect.pre的后续执行需要依赖width。

$effect.active

官网给出的说明是用于判断是否在一个effect中或是否在template中。

html 复制代码
<script>
  let count = $state(0);

  $effect(() => {
    console.log('effect', count);
    console.log('isActive in $effect', $effect.active());
  });

  $effect.pre(() => {
    console.log('pre', count);
    console.log('isActive in $effect.pre', $effect.active());
  });

  let double = $derived.by(() => {
    console.log('isActive in derived.by', $effect.active());
    return count * 2;
  })

  console.log('isActive no runes', $effect.active());
</script>

<input type="number" bind:value={count} />
double: {double}
in template:{console.log('isActive in template', $effect.active())}

经过试验,在$effect$effect.pre和template中能够正常判断。我们尝试在$derived.by中使用,可是打印结果提醒我们$effect.active在其中并不适用。

$effect.root

使用#effect.root,我们可以手动地控制$effect的生效与否。

html 复制代码
<script>
let count = $state(0);

const offEffect = $effect.root(() => {
    $effect(() => {
        console.log(count);
    });

    return () => {
        console.log('关闭$effect');
    };
});
</script>

<button onclick={() => offEffect()}>关闭$effect</button>
<button onclick={() => count++}>更新</button>

如果不调用$effect.root的返回函数,依旧正常追踪依赖更新。当我们执行了返回函数后,可以看到$effect不再执行。

$props

很明显,用来接收props的Rune。

Svelte 4中:

html 复制代码
<script>
  export let value;
</script>

子组件:{value}

Svelte 5中:

html 复制代码
<script>
  let { value } = $props();
</script>

子组件:{value}

$inspect

当状态更新时,会打印相关信息。

html 复制代码
<script>
    let count = $state(0);
    $inspect(count); // 当count变化时console.log出来
</script>

<button onclick={() => count++}>add count</button>

Snippets

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

html 复制代码
<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()}来复用定义好的片段。

传递给组件

能够把片段当成一个属性传递给组件。

我们定义一个组件,可以接受header和footer片段参数:

html 复制代码
<script>
  let { header, footer } = $props();
</script>

<div>
  <header>{@render header()}</header>
  组件内部
  <footer>{@render footer()}</footer>
</div>

将我们定义好的片段传递给组件:

html 复制代码
<script>
    import Svelte5 from './Svelte5.svelte';
</script>

{#snippet header()}
    <div>头部内容</div>
{/snippet}

{#snippet footer()}
    <div>尾部内容</div>
{/snippet}

<Svelte5 {header} {footer} />

事件

事件监听

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

diff 复制代码
<script>
  const onClick = () => {
    console.log('click');
  }
</script>

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

组件事件

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

html 复制代码
<script>
  let { onClick, onClick2 } = $props();
</script>

<button onclick={onClick}>click</button>
<button onclick={e => onClick2('hello svelte')}>click2</button>
html 复制代码
<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()来接收插槽内容。

html 复制代码
<script>
  let { children } = $props();
</script>

<div>
  <header>头部</header>
  <main>{@render children() }</main>
  <footer>底部</footer>
</div>
html 复制代码
<script>
    import Svelte5 from './Svelte5.svelte';
</script>

<Svelte5>
    内容
</Svelte5>

这里的{@render ...}和后面介绍的Snippets有关。

事件修饰符

Svelte4的事件修饰符如下:

html 复制代码
<button on:click|once|preventDefault={handler}>...</button>

像once、preventDefault、stopPropagation等需要自己手动实现:

html 复制代码
<script>
function once(fn) {
    return function (event) {
        if (fn) fn.call(this, event);
        fn = null;
    };
}

function preventDefault(fn) {
    return function (event) {
        event.preventDefault();
        fn.call(this, event);
    };
}
</script>

<button onclick={once(preventDefault(handler))}>...</button>

capturepassivenonpassive等,Svelte5仍提供了对应的事件修饰符:

html 复制代码
<button onclickcapture={...}>...</button>

说实话,不太美观。

方法

untrack

html 复制代码
<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的依赖。

html 复制代码
<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} />

unstate

把使用$state()生成的响应式数据(对象或数组)变成非响应式的。

演示一个Svelte 4的例子:

html 复制代码
<script>
  // svelte 4
  export let obj = {
    name: 'text'
  };

  const onUpdate = () => {
    obj.name = '' + Math.random();
  }

  $: console.log('obj change', obj.name);
</script>

<button on:click={onUpdate}>update</button><br />
obj: {obj.name}<br />

额外话题:如果我们需要$:生效,需要$: console.log(obj.name)而不是$: console.log(obj)

演示一下unstate的功能:

html 复制代码
<script>
  // svelte 5
  import { unstate } from 'svelte';

  let obj = $state({
    name: 'text'
  });
  let _obj = unstate(obj);

  const onUpdate = () => {
    obj.name = '' + Math.random();
    _obj.name = '' + Math.random();
  }

  $effect(() => {
    console.log('$state data change', obj.name);
  });

  $effect(() => {
    console.log('unstate data cahnge', _obj.name);
  })

</script>

<button onclick={onUpdate}>update</button><br />
obj: {obj.name}<br />
_obj: {_obj.name}

使用了unstate的数组在赋值后,$effect不会追踪这部分非响应式数据。

mount

Svelte 4中挂载App:

javascript 复制代码
import { mount } from 'svelte';
import App from './App.svelte';

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

Svelte 5中挂载App:

javascript 复制代码
import App from './App.svelte'

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

export default app

hydrate

SSR使用。

javascript 复制代码
import { hydrate } from 'svelte';
import App from './App.svelte';

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

render

服务端渲染时使用。接收一个Component并返回一个带有html和head参数的对象。

javascript 复制代码
import { render } from 'svelte/server';
import App from './App.svelte';

const result = render(App, {	
    props: { some: 'property' }
});

总结

Svelte 5移除了onMountbeforeUpdateafterUpdate等生命周期,移除了createDispatcher,将组件的方法通过$props来传递,让我们不用再关注CustomEvent.detail等细节,很明显降低了学习的曲线。同时引入了Runes的概念,对数据状态能够进行更为颗粒度的控制,引入了Snippets,可以对页面内容进行Element层级的复用。

文章只介绍了较为关键的特性,更多细节内容,还请感兴趣的读者们去官网深度探索。

参考

相关推荐
浮华似水12 分钟前
简洁之道 - React Hook Form
前端
正小安2 小时前
如何在微信小程序中实现分包加载和预下载
前端·微信小程序·小程序
_.Switch4 小时前
Python Web 应用中的 API 网关集成与优化
开发语言·前端·后端·python·架构·log4j
一路向前的月光4 小时前
Vue2中的监听和计算属性的区别
前端·javascript·vue.js
长路 ㅤ   4 小时前
vite学习教程06、vite.config.js配置
前端·vite配置·端口设置·本地开发
长路 ㅤ   4 小时前
vue-live2d看板娘集成方案设计使用教程
前端·javascript·vue.js·live2d
Fan_web4 小时前
jQuery——事件委托
开发语言·前端·javascript·css·jquery
安冬的码畜日常4 小时前
【CSS in Depth 2 精译_044】第七章 响应式设计概述
前端·css·css3·html5·响应式设计·响应式
莹雨潇潇5 小时前
Docker 快速入门(Ubuntu版)
java·前端·docker·容器
Jiaberrr5 小时前
Element UI教程:如何将Radio单选框的圆框改为方框
前端·javascript·vue.js·ui·elementui