Svelte: Знакомство с рунами

от автора

Эта статья — перевод оригинальной статьи «Introducing runes«.

Также я веду телеграм канал “Frontend по-флотски”, где рассказываю про интересные вещи из мира разработки интерфейсов.

Вступление

В 2019 году Svelte 3 превратил JavaScript в реактивный язык. Svelte — это фреймворк для создания веб-интерфейса, который использует компилятор для превращения декларативного кода компонентов в такой…

<script> let count = 0;  function increment() { count += 1; } </script>  <button on:click={increment}> clicks: {count} </button>

…в жестко оптимизированный JavaScript, который обновляет документ при изменении состояния, например, count. Поскольку компилятор «видит», где ссылаются на count, генерируемый код очень эффективен, а поскольку мы используем такие синтаксисы, как let и =, а не громоздкие API, вы можете писать меньше кода.

Чаще всего мы получаем такие отзывы: «Хотел бы я писать весь свой JavaScript именно так». Когда вы привыкли к тому, что вещи внутри компонентов волшебным образом обновляются, возврат к старому скучному процедурному коду кажется вам переходом от цветного к черно-белому.

В Svelte 5 все это изменилось благодаря рунам, которые открывают универсальную, тонкую реактивность.

Прежде чем начать

Несмотря на то, что мы меняем принцип работы под капотом, Svelte 5 должен стать полноценной заменой практически для всех. Новые возможности являются опциональными — существующие компоненты будут продолжать работать.

Дата выхода Svelte 5 пока не определена. То, что мы показываем здесь, — это наработки, которые, могут измениться.

Что такое руны?

Руны — это символы, влияющие на работу компилятора Svelte. Если сегодня в Svelte для обозначения конкретных вещей используются let, =, ключевое слово export и метка $:, то руны используют синтаксис функций для достижения того же и даже большего.

Например, чтобы объявить часть реактивного состояния, мы можем использовать руну $state:

<script> // let count = 0; let count = $state(0);  function increment() { count += 1; } </script>  <button on:click={increment}> clicks: {count} </button> 

На первый взгляд, это может показаться шагом назад — возможно, даже не по-свельтовски. Не лучше ли, если let count будет реактивным по умолчанию?

Нет. Реальность такова, что по мере роста сложности приложений выяснение того, какие значения являются реактивными, а какие нет, может стать сложной задачей. К тому же эвристика работает только для объявлений let на верхнем уровне компонента, что может привести к путанице. Если в файлах .svelte код ведет себя одним образом, а в .js — другим, это может затруднить рефакторинг кода, например, если вам нужно превратить что-то в хранилище, чтобы использовать его в нескольких местах.

За пределами компонентов

С помощью рун реактивность выходит за пределы файлов .svelte. Предположим, мы хотим инкапсулировать логику счетчика таким образом, чтобы ее можно было повторно использовать в разных компонентах. Сегодня для этого используется кастомное хранилище в файле .js или .ts:

import { writable } from 'svelte/store';  export function createCounter() { const { subscribe, update } = writable(0);  return { subscribe, increment: () => update((n) => n + 1) }; }

Поскольку в данном случае реализуется контракт с хранилищем — возвращаемое значение имеет метод subscribe, — мы можем ссылаться на значение хранилища, добавляя к его имени префикс $:

<script> import { createCounter } from './counter.js';  const counter = createCounter(); // let count = 0;  // function increment() { // count += 1; // } </script>  // <button on:click={increment}> // clicks: {count} <button on:click={counter.increment}> clicks: {$counter} </button> 

Это работает, но довольно странно! Мы обнаружили, что API хранилища может стать довольно громоздким, когда вы начинаете делать более сложные вещи.

С рунами все гораздо проще:

// import { writable } from 'svelte/store';  export function createCounter() { // const { subscribe, update } = writable(0); let count = $state(0);  return { // subscribe, // increment: () => update((n) => n + 1) get count() { return count }, increment: () => count += 1    }; } 
<script> import { createCounter } from './counter.js';  const counter = createCounter(); </script>  <button on:click={counter.increment}> // clicks: {$counter} clicks: {counter.count} </button> 

Обратите внимание, что мы используем свойство get в возвращаемом объекте, поэтому counter.count всегда ссылается на текущее значение, а не на значение в момент вызова функции.

Реактивность во время выполнения

Сегодня Svelte использует реактивность во время компиляции. Это означает, что если у вас есть код, использующий метку $: для автоматического перезапуска при изменении зависимостей, то эти зависимости будут определены при компиляции компонента в Svelte:

<script> export let width; export let height;  // компилятор знает, что ему следует пересчитать `площадь`. // при изменении `ширины` или `высоты`... $: area = width * height;  // ...и что он должен логировать значение `area`. // когда оно меняется $: console.log(area); </script>

Это работает хорошо… до того момента пока не перестанет. Предположим, что мы рефакторим приведенный выше код:

const multiplyByHeight = (width) => width * height; $: area = multiplyByHeight(width);

Поскольку объявление $: area = … может «видеть» только width, оно не будет пересчитываться при изменении height. В результате код трудно рефакторить, а понимание тонкостей того, когда Svelte решает обновить те или иные значения, может стать довольно сложным после определенного уровня сложности.

В Svelte 5 появились руны $derived и $effect, которые вместо этого определяют зависимости своих выражений при их вычислении:

<script> let { width, height } = $props(); // вместо `export let`  const area = $derived(width * height);  $effect(() => { console.log(area); }); </script>

Как и $state, $derived и $effect также могут быть использованы в файлах .js и .ts.

Усиление сигнала

Как и любой другой фреймворк, мы пришли к пониманию того, что Knockout всегда был прав.

Реактивность Svelte 5 обеспечивается сигналами, которые, по сути, являются тем, чем занимался Knockout в 2010 году. Совсем недавно сигналы были популяризированы Solid и приняты множеством других фреймворков.

Однако у нас все немного по-другому. В Svelte 5 сигналы — это детали реализации, а не то, с чем вы взаимодействуете напрямую. Таким образом, мы не имеем тех же ограничений на дизайн API и можем максимально повысить эффективность и эргономичность. Например, мы избегаем проблем с сужением типов, возникающих при обращении к значениям через вызов функции, а при компиляции в режиме рендеринга на стороне сервера мы можем вообще отказаться от сигналов, поскольку на сервере они — не более чем накладные расходы.

Сигналы позволяют реализовать мелкозернистую реактивность, то есть, например, изменение значения в большом списке не обязательно должно приводить к аннулированию всех остальных членов списка. Таким образом, Svelte 5 работает невероятно быстро.

Впереди более простые времена

Руны — это добавочное свойство, но они делают устаревшими целую кучу существующих концепций:

  • разница между let на верхнем уровне компонента и в остальных местах

  • export let

  • $:, со всеми вытекающими отсюда странностями

  • разное поведение между <script> и <script context="module">

  • API хранилища, часть которого на самом деле довольно сложна

  • префикс хранилища $

  • $$props и $$restProps

  • функции жизненного цикла (такие вещи, как onMount, могут быть просто функциями $effect)

Для тех, кто уже использует Svelte, это новые знания, хотя, надеюсь, они облегчат создание и поддержку приложений Svelte. Но новичкам не нужно будет изучать все эти вещи — они просто будут находиться в разделе документации под названием «старые вещи».

Однако это только начало. У нас есть большой список идей для последующих релизов, которые сделают Svelte еще проще и функциональнее.

Попробуй Svelte 5!

Пока что Svelte 5 нельзя использовать в продакшене. Мы сейчас находимся в гуще событий и не можем сказать, когда он будет готов к использованию в ваших приложениях.

Но мы не хотели оставлять вас в подвешенном состоянии. Мы создали предварительный сайт с подробным описанием новых возможностей и интерактивной игровой площадкой. Вы также можете посетить канал #svelte-5-runes в Svelte Discord, чтобы узнать больше. Мы будем рады получить ваши отзывы!


ссылка на оригинал статьи https://habr.com/ru/articles/763256/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *