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

Предупреждения рантайма

Assignment to `%property%` property (%location%) will evaluate to the right-hand side, not the value of `%property%` following the assignment. This may result in unexpected behaviour.

Рассмотрим следующий случай:

<script>
let object = $state({ array: null });
function add() {
(object.array ??= []).push(object.array.length);
}
</script>
<button onclick={add}>добавить</button>
<p>элементы: {JSON.stringify(object.items)}</p>

…массив, в который происходит добавление при первом клике на кнопку, — это [] в правой части присваивания, но итоговое значение object.array становится пустым прокси-объектом состояния. В результате добавленное значение будет потеряно.

Исправить это можно, разделив операцию на два отдельных выражения:

function add() {
object.array ??= [];
object.array.push(object.array.length);
}
Detected reactivity loss when reading `%name%`. This happens when state is read in an async function after an earlier `await`

Реактивность Svelte на основе сигналов работает за счёт отслеживания, какие части состояния считываются при выполнении шаблона или выражения $derived(...). Если выражение содержит await, Svelte преобразует его таким образом, что любое состояние после await также отслеживается. Другими словами, в случае такого кода:

let total = $derived(await a + b);

…и a, и b отслеживаются, несмотря на то что b считывается только после разрешения a, после начального выполнения.

Это правило не применяется к await, который не «виден» внутри выражения. Например:

async function sum() {
return await a + b;
}
let total = $derived(await sum());

total будет зависеть от a (которая считывается сразу), но не от b (которая не считывается). Решение — передавать значения в функцию явно:

async function sum(a, b) {
return await a + b;
}
let total = $derived(await sum(a, b));
An async derived, `%name%` (%location%) was not read immediately after it resolved. This often indicates an unnecessary waterfall, which can slow down your app

В таком случае…

let a = $derived(await one());
let b = $derived(await two());

…второй $derived не будет создан до тех пор, пока не разрешится первый. Поскольку await two() не зависит от значения a, эта задержка (часто называемая «каскадом») является избыточной.

(Примечание: если значения await one() и await two() впоследствии изменятся, они смогут делать это параллельно — «каскад» возникает только при первоначальном создании производных.)

Решить это можно, сначала создав промисы, а затем ожидая их:

let aPromise = $derived(one());
let bPromise = $derived(two());
let a = $derived(await aPromise);
let b = $derived(await bPromise);
`%binding%` is binding to a non-reactive property
`%binding%` (%location%) is binding to a non-reactive property
Your `console.%method%` contained `$state` proxies. Consider using `$inspect(...)` or `$state.snapshot(...)` instead

При логировании прокси инструменты разработчика в браузере выводят сам прокси, а не значение, которое он представляет. В случае со Svelte, target (целевой объект) прокси $state может не соответствовать текущему значению, что может сбивать с толку.

Самый простой способ залогировать изменяющееся значение — использовать руну $inspect. В качестве альтернативы, для разового логирования (например, в обработчике событий) можно использовать $state.snapshot, чтобы получить снимок текущего значения.

%handler% should be a function. Did you mean to %suggestion%?
The `%attribute%` attribute on `%html%` changed its value between server and client renders. The client value, `%value%`, will be ignored in favour of the server value

Некоторые атрибуты, такие как src у элемента <img>, не будут обновляться во время гидратации — серверное значение останется без изменений. Это происходит потому, что обновление этих атрибутов может привести к повторной загрузке изображения (или, в случае <iframe>, к перезагрузке фрейма), даже если они ссылаются на тот же ресурс.

Чтобы исправить это:

  • Либо отключите предупреждение комментарием svelte-ignore
  • Либо убедитесь, что значение одинаково на сервере и клиенте

Если вам действительно нужно изменить значение при гидратации, можно принудительно обновить его так:

<script>
let { src } = $props();
if (typeof window !== 'undefined') {
// сохраняем исходное значение...
const initial = src;
// сбрасываем его...
src = undefined;
$effect(() => {
// ...и восстанавливаем после монтирования
src = initial;
});
}
</script>
<img {src} />
The value of an `{@html ...}` block changed between server and client renders. The client value will be ignored in favour of the server value
The value of an `{@html ...}` block %location% changed between server and client renders. The client value will be ignored in favour of the server value

Если значение {@html ...} отличается на сервере и клиенте, оно не будет исправлено при гидратации — останется серверное значение. Это происходит потому, что обнаружение изменений во время гидратации требует много ресурсов и обычно не нужно.

Чтобы исправить это:

  • Либо отключите предупреждение комментарием svelte-ignore
  • Либо убедитесь, что значение одинаково на сервере и клиенте

Если вам действительно нужно изменить значение при гидратации, можно принудительно обновить его так:

<script>
let { markup } = $props();
if (typeof window !== 'undefined') {
// сохраняем исходное значение...
const initial = markup;
// сбрасываем его...
markup = undefined;
$effect(() => {
// ...и восстанавливаем после монтирования
markup = initial;
});
}
</script>
{@html markup}
Hydration failed because the initial UI does not match what was rendered on the server
Hydration failed because the initial UI does not match what was rendered on the server. The error occurred near %location%

Это предупреждение появляется, когда Svelte сталкивается с ошибкой при гидратации HTML с сервера. Во время гидратации Svelte анализирует DOM, ожидая определённой структуры. Если структура отличается (например, из-за того, что браузер автоматически исправил некорректный HTML), Svelte не сможет корректно выполнить гидратацию, что приводит к этому предупреждению.

В режиме разработки этой ошибке часто предшествует console.error с подробным описанием проблемного HTML, который требует исправления.

The `render` function passed to `createRawSnippet` should return HTML for a single element
Detected a migrated `$:` reactive block in `%filename%` that both accesses and updates the same reactive value. This may cause recursive updates when converted to an `$effect`.
Tried to unmount a component that was not mounted
%parent% passed property `%prop%` to %child% with `bind:`, but its parent component %owner% did not declare `%prop%` as a binding. Consider creating a binding between %owner% and %parent% (e.g. `bind:%prop%={...}` instead of `%prop%={...}`)

Рассмотрим три компонента: GrandParent, Parent и Child. Если вы используете <GrandParent bind:value>, внутри GrandParent передаёте переменную через <Parent {value} /> (обратите внимание на отсутствие bind:), а затем используете <Child bind:value> внутри Parent, то будет вызвано это предупреждение.

Чтобы исправить ситуацию, используйте bind: при передаче значения вместо простого свойства (в данном примере следует сделать <Parent bind:value />).

Mutating unbound props (`%name%`, at %location%) is strongly discouraged. Consider using `bind:%prop%={...}` in %parent% (or using a callback) instead

Рассмотрим следующий код:

App.svelte
<script>
import Child from './Child.svelte';
let person = $state({ name: 'Florida', surname: 'Man' });
</script>
<Child {person} />
Child.svelte
<script>
let { person } = $props();
</script>
<input bind:value={person.name}>
<input bind:value={person.surname}>

Компонент Child изменяет (мутирует) объект person, который принадлежит компоненту App, без явного «разрешения» на это. Такой подход крайне не рекомендуется, так как может привести к коду, который сложно анализировать в больших масштабах («кто изменил это значение?»), отсюда и предупреждение.

Чтобы исправить это:

  • Либо используйте callback-пропсы для передачи изменений
  • Либо пометьте person как $bindable
The `value` property of a `<select multiple>` element should be an array, but it received a non-array value. The selection will be kept as is.

При использовании <select multiple value={...}>, Svelte пометит все выбранные элементы <option> как выбранные, перебирая массив, переданный в value. Если value не является массивом, Svelte выдаст предупреждение и оставит выбранные опции без изменений.

Чтобы подавить предупреждение, убедитесь, что value:

  • является массивом для явного выбора
  • равно null или undefined, чтобы сохранить текущий выбор
Reactive `$state(...)` proxies and the values they proxy have different identities. Because of this, comparisons with `%operator%` will produce unexpected results

$state(...) создает прокси для переданного значения. Прокси и исходное значение имеют разные идентификаторы, поэтому проверки на равенство всегда будут возвращать false:

<script>
let value = { foo: 'bar' };
let proxy = $state(value);
value === proxy; // всегда false
</script>

Чтобы решить эту проблему, убедитесь, что вы сравниваете значения, которые либо оба созданы через $state(...), либо оба не являются прокси. Обратите внимание, что $state.raw(...) не создаёт прокси состояния.

The `slide` transition does not work correctly for elements with `display: %value%`

Переход slide работает за счёт анимации свойства height, что требует стиля display со значением block, flex или grid. Он не работает для:

  • display: inline (значение по умолчанию для элементов вроде <span>) и его вариантов: inline-block, inline-flex, inline-grid
  • display: table и table-[name] (значения по умолчанию для <table>, <tr> и др.)
  • display: contents
`<svelte:element this="%tag%">` is a void element — it cannot have content

Элементы типа <input> не могут содержать дочерние элементы — любой переданный им контент будет проигнорирован.

Value cannot be cloned with `$state.snapshot` — the original value was returned
The following properties cannot be cloned with `$state.snapshot` — the return value contains the originals:
%properties%

$state.snapshot пытается клонировать переданное значение, чтобы вернуть неизменяемую ссылку. Некоторые объекты не могут быть клонированы — в этом случае возвращается исходное значение. В следующем примере property клонируется, а window — нет, поскольку DOM-элементы невозможно клонировать:

const object = $state({ property: 'это можно клонировать', window })
const snapshot = $state.snapshot(object);