Перейти к содержимому

Лучшие практики

Этот документ описывает некоторые лучшие практики, которые помогут вам писать быстрые и надёжные приложения на Svelte. Он также доступен как навык svelte-core-bestpractices для ваших агентов.

Используйте руну $state только для переменных, которые должны быть реактивными — то есть для переменных, изменение которых должно вызывать обновление $effect, $derived или выражений в шаблоне. Всё остальное можно оставить обычными переменными.

Объекты и массивы ($state({...}) или $state([...])) становятся глубоко реактивными, то есть мутация их содержимого будет запускать обновления. Здесь есть компромисс: за тонкую реактивность приходится платить тем, что объекты оборачиваются в прокси, а это создаёт накладные расходы на производительность. В случаях, когда вы работаете с большими объектами, которые только переприсваиваются целиком (а не мутируются), лучше использовать $state.raw. Это особенно часто встречается, например, при работе с ответами от API.

Для вычисления значений на основе состояния используйте $derived, а не $effect:

// правильно
let square = $derived(num * num);
// неправильно
let square;
$effect(() => {
square = num * num;
});

Derived-значения можно перезаписывать — вы можете присваивать им значения точно так же, как и $state, но при изменении их выражения они будут перевычисляться заново.

Если выражение внутри derived возвращает объект или массив, он возвращается как есть — он не становится глубоко реактивным. Однако в редких случаях, когда это всё-таки нужно, вы можете использовать $state внутри $derived.by.

Эффекты — это запасной выход, и в большинстве случаев их лучше избегать. В частности, не обновляйте состояние внутри эффектов.

  • Если нужно синхронизировать состояние с внешней библиотекой (например, D3), часто удобнее использовать {@attach ...}
  • Если код должен выполняться в ответ на действие пользователя, размещайте его прямо в обработчике события или используйте привязку функции там, где это уместно
  • Если нужно логировать значения для отладки, используйте $inspect
  • Если требуется наблюдать за чем-то внешним по отношению к Svelte, используйте createSubscriber

Никогда не оборачивайте содержимое эффекта в if (browser) {...} и подобные проверки — эффекты не выполняются на сервере.

Относитесь к пропсам так, будто они могут измениться в любой момент. Например, значения, зависящие от пропсов, в большинстве случаев должны использовать $derived:

let { type } = $props();
// правильно
let color = $derived(type === 'danger' ? 'red' : 'green');
// неправильно — `color` не обновится, если изменится `type`
let color = type === 'danger' ? 'red' : 'green';

$inspect.trace — это инструмент отладки реактивности. Если что-то не обновляется должным образом или выполняется чаще, чем должно, вы можете добавить $inspect.trace(label) в качестве первой строки внутри $effect или $derived.by (или любой функции, которую они вызывают). Это позволит отследить их зависимости и выяснить, какая именно из них запустила обновление.

Любой атрибут элемента, начинающийся с on, рассматривается как слушатель события:

<button onclick={() => {...}}>click me</button>
<!-- сокращённая запись атрибута тоже работает -->
<button {onclick}>...</button>
<!-- так же работают spread-атрибуты -->
<button {...props}>...</button>

Если вам нужно прикрепить слушатели событий к window или document, вы можете использовать <svelte:window> и <svelte:document>:

<svelte:window onkeydown={...} />
<svelte:document onvisibilitychange={...} />

Избегайте использования onMount или $effect для этого.

Сниппеты — это способ определить повторно используемые фрагменты разметки, которые можно вызывать с помощью тега {@render ...} или передавать компонентам через пропсы. Они должны быть объявлены внутри шаблона.

{#snippet greeting(name)}
<p>привет, {name}!</p>
{/snippet}
{@render greeting('world')}

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

Избегайте деструктуризации, если вам нужно изменять (мутировать) элемент (например, при использовании bind:value={item.count} и подобных конструкций).

Если у вас есть переменная JavaScript, которую вы хотите использовать внутри CSS, вы можете задать пользовательское свойство с помощью директивы style:.

<div style:--columns={columns}>...</div>

Вы можете затем использовать var(--columns) внутри компонента <style>.

CSS в теге компонента <style> ограничен областью видимости только этого компонента (scoped). Если родительскому компоненту нужно управлять стилями дочернего, предпочтительный способ — использовать пользовательские свойства CSS:

Parent.svelte
<Child --color="red" />
<!-- Child.svelte -->
<h1>Привет</h1>
<style>
h1 {
color: var(--color);
}
</style>

Если это невозможно (например, дочерний компонент берётся из библиотеки), вы можете использовать :global, чтобы переопределить стили:

<div>
<Child />
</div>
<style>
div :global {
h1 {
color: red;
}
}
</style>

Рассматривайте использование контекста вместо объявления состояния в общем модуле. Это ограничит область действия состояния той частью приложения, которой оно действительно нужно, и исключит риск утечки состояния между пользователями при серверном рендеринге.

Используйте createContext вместо setContext и getContext, поскольку он обеспечивает типобезопасность.

Если вы используете версию 5.36 или выше, вы можете применять await-выражения и данные с поддержкой гидратации, чтобы напрямую работать с промисами внутри компонентов.

Всегда используйте режим рун (runes mode) для нового кода и старайтесь избегать возможностей, у которых уже есть более современные замены:

  • используйте $state вместо неявной реактивности (например, let count = 0; count += 1)
  • используйте $derived и $effect вместо $:-присваиваний и $:-выражений (но применяйте эффекты только тогда, когда нет лучшего решения)
  • используйте $props вместо export let, $$props и $$restProps
  • используйте onclick={...} вместо on:click={...}
  • используйте {#snippet ...} и {@render ...} вместо <slot>, $$slots и <svelte:fragment>
  • используйте <DynamicComponent> вместо <svelte:component this={DynamicComponent}>
  • используйте import Self from './ThisComponent.svelte' и <Self> вместо <svelte:self>
  • используйте классы с полями $state для передачи реактивности между компонентами вместо использования stores
  • используйте {@attach ...} вместо use:action
  • используйте массивы и объекты в стиле clsx в атрибутах class вместо директивы class: