Svelte 5.0 全新响应式API,Signal 是未来?

Svelte 是一个 Web 开发框架,无虚拟 dom,同时提供了许多开箱即用的功能,感兴趣的可以查看 Svelte 文档

Svelte 前几天发布了最新的 V5.0 大版本,其中最核心的修改就是重构了它的响应式系统。Svelte 把它起名叫做 runes, 所有 runes API 都使用 $ 开头。

今天一起来学习一下全新的 runes 响应式 API。

$state

在旧版本中,直接使用 let 就可以定义一个响应式变量:

ts 复制代码
<script>
  // 像普通变量一样直接用 let 定义
	let counter = 1;
	function increment() {
		counter++;
	}
</script>
<div>
	<button onclick={increment}>Increment With legacy</button>
	<p>Counter With legacy: {counter}</p>
</div>

在 Svelte5 中,需要使用 $state 来定义响应式变量:

ts 复制代码
<script>
	let counter2 = $state(1);
	function increment2() {
	    counter2++;
	}
</script>
<div>
    <button onclick={increment2}>Increment With new</button>
    <p>Counter With new: {counter2}</p>
</div>

那么在 Svelte5 中,旧版写法是否还兼容呢。 是这样的,如果你不使用 runes,仍然是可以用旧版写法的。如果同时使用旧版写法和新版写法,就会失去响应式,变成一个普通变量:

ts 复制代码
<script>
  // 这是一个普通变量,不具有响应式
	let counter = 1;
	function increment() {
		counter++;
	}
  // 和 runes 并存
	let counter2 = $state(1);
	function increment2() {
	    counter2++;
	}
</script>
<div>
	<button onclick={increment}>Increment With legacy</button>
	<p>Counter With legacy: {counter}</p>
</div>
<div>
    <button onclick={increment2}>Increment With new</button>
    <p>Counter With new: {counter2}</p>
</div>

使用外部 *.svelte.js 中的 runes

在 Svelte5 中,由于有了 runes,你可以在组件中导入外部 *.svelte.js 或者 *.svelte.ts 中的 runes 来使用,这在 Svelte4 中无法做到。

ts 复制代码
export const rune = $state({
	count2: 1
});

export function increment2() {
	rune.count2++;
}

在 svelte 组件中直接导入就可以了:

ts 复制代码
<script>
	import { rune, increment2 } from './runes-in-js.svelte.js';
</script>
<div>
	<button onclick={increment2}>Increment the rune inside js</button>
	<p>Counter2: {rune.count2}</p>
</div>

$derived

$derived 类似于 Vue 中的计算属性:

ts 复制代码
<script>
	let counter = $state(1);
	function increment() {
		counter++;
	}

  // 自动计算 counter * 2 的结果
  let counterTimesTwo = $derived(counter * 2);
</script>
<div>
	<button onclick={increment}>Increment the new way</button>
	<p>Counter: {counter}</p>
	<p>Counter Times Two: {counterTimesTwo}</p>
</div>

$state 的其他用法

当然可以用来创建一个对象:

ts 复制代码
<script>
// 基本类型
let count = $state(1)
// 可以创建对象
let user = $state({ name: 'John', age: 30 });
</script>

$state.raw 用于创建一个浅层(shallow)响应式对象。 这意味着,这个对象是响应式的,但是它的属性不是,你只可以重新设置整个对象来触发响应式。

ts 复制代码
<script>
let shallowState = $state.raw(['Alice', 'Bob', 'Charlie']);

// 这将不会变化。
shallowState.push('Dave');

// It works!
shallowState = [...shallowState, 'Dave'];
</script>

$state.snapshot 用于获取一个响应式对象的原始类型(非响应式)。

ts 复制代码
<script>
const originalObject = { name: 'John', age: 30 };
const reactiveState = $state(originalObject);
let snapshot = $state.snapshot(reactiveState);
</script>

$effect

$effect 类似于 React 的 useEffect,区别是会自动收集响应式依赖。

ts 复制代码
<script>
	$effect(() => {
		console.log('effect: counterTimesTwo is now', counterTimesTwo);
		return () => {
			console.log('Component unmounted');
		};
	});
</script>

$effect.pre 用于在 DOM 更新之前运行代码:

ts 复制代码
<script>
	$effect.pre(() => {
		console.log('effect.pre: counterTimesTwo is now', counterTimesTwo);
	});
</script>

$effect.root 将会创建一个 effect root,它不会在组件卸载时自动释放,你可以手动调用 cleanup() 来释放

ts 复制代码
<script>
	const cleanup = $effect.root(() => {
		console.log('effect.root: counterTimesTwo is now', counterTimesTwo);
		$effect(() => {
			console.log('effect.root: counterTimesTwo is now', counterTimesTwo);

			return () => {
				console.log('effect.root - $effect: effect unmounted');
			};
		});

		return () => {
			console.log('effect.root: Component unmounted');
		};
	});
</script>

<button onclick={() => cleanup()}>Dispose effect.root</button>

$props

在 Svelte4 中,使用 export let value 来定义组件 props。

在 Svelte5 中,使用 $props 来定义组件 props。

子组件:

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

<button onclick={() => (count += 1)}>
	clicks (child): {count}
</button>

父组件:

ts 复制代码
<script>
	import Child from './Child.svelte';

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

<button onclick={() => (count += 1)}>
	clicks (parent): {count}
</button>

<Child {count} />

父组件点击增加 count,会传给子组件,而子组件点击增加,不会反映给父组件,此时是一个单向的数据流。

使用 $bindable 实现双向绑定

上面说了,默认 props 是单向数据流,子组件无法修改父组件的数据,这也是合理的。

那么我们常见的一些表单实现效果如下,实现一个受控 Input 组件。

子组件定义 onChange props, 把更改后的值通过 onChange 传回去:

ts 复制代码
<script lang="ts">
  interface Props {
		value: string;
    onChange: (value: string) => void;
	}
	let { value, onChange }: Props = $props();
</script>

<input type="text" {value} onchange={(event) => {
  onChange((event.target as HTMLInputElement).value);
}}/>

父组件传 onChange,接受子组件回传的值:

ts 复制代码
<script>
	import Input from './Input.svelte';

	let value = $state('123');
</script>

<Input {value} onChange={(e) => {
  console.log('child changed to', e);
  value = e;
}}/>

熟悉 React 同学看的就很眼熟,我们经常这么写。

而在 Vue 中,有 v-model 可以实现双向绑定。 在 Svelte5 中,可以使用 $bindable 实现双向绑定:

ts 复制代码
<script>
	let { value = $bindable(''), ...rest } = $props();
</script>

<input type="text" bind:value />

父组件中的 value 也被修改了:

ts 复制代码
<script>
	import Input from './Input.svelte';
	let value = $state('');
  // `$inspect` 也是一个 runes,用于调试打印。类似 `$effect(() => console.log(value))`
	$inspect(value);
</script>
<Input bind:value />

$host

$host 是一个高级特性,当组件被编译为自定义元素时,用来获取当前组件的引用,发送自定义事件。

ts 复制代码
<svelte:options customElement="my-stepper" />

<script>
	function dispatch(type) {
		$host().dispatchEvent(new CustomEvent(type));
	}
</script>

<button onclick={() => dispatch('decrement')}>decrement</button>
<button onclick={() => dispatch('increment')}>increment</button>

最后

Svelte5 的 runes $state,Vue3 的组合式 API ref,Solid 的 signal createSignal,Angular Signals signal,似乎除了 React 都拥抱了 signal,你觉得这套东西怎么样呢。

相关推荐
理想不理想v9 分钟前
【经典】webpack和vite的区别?
java·前端·javascript·vue.js·面试
羊子雄起20 分钟前
CKEditor前端样式和编辑器的样式不一致的问题
前端·编辑器
聊无生35 分钟前
JavaSrcipt 函数高级
开发语言·前端·javascript
xiyusec1 小时前
HTML基础
前端·html
好开心331 小时前
javaScript交互案例2
开发语言·前端·javascript·html·ecmascript·交互
xChive1 小时前
优化表单交互:在 el-select 组件中嵌入表格显示选项
前端·vue.js·交互·element-plus
tian-ming1 小时前
(十八)JavaWeb后端开发案例——会话/yml/过滤器/拦截器
java·开发语言·前端
duansamve1 小时前
WebGIS地图框架有哪些?
javascript·gis·openlayers·cesium·mapbox·leaflet·webgis
_jacobfu2 小时前
mac2024 安装node和vue
前端·javascript·vue.js
Ztiddler2 小时前
【npm设置代理-解决npm网络连接error network失败问题】
前端·后端·npm·node.js·vue