Предупреждения рантайма
Клиентские предупреждения
Заголовок раздела «Клиентские предупреждения»assignment_value_stale
Заголовок раздела «assignment_value_stale»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);}
await_reactivity_loss
Заголовок раздела «await_reactivity_loss»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));
await_waterfall
Заголовок раздела «await_waterfall»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_property_non_reactive
Заголовок раздела «binding_property_non_reactive»`%binding%` is binding to a non-reactive property
`%binding%` (%location%) is binding to a non-reactive property
console_log_state
Заголовок раздела «console_log_state»Your `console.%method%` contained `$state` proxies. Consider using `$inspect(...)` or `$state.snapshot(...)` instead
При логировании прокси инструменты разработчика в браузере выводят сам прокси, а не значение, которое он представляет. В случае со Svelte, target
(целевой объект) прокси $state
может не соответствовать текущему значению, что может сбивать с толку.
Самый простой способ залогировать изменяющееся значение — использовать руну $inspect
. В качестве альтернативы, для разового логирования (например, в обработчике событий) можно использовать $state.snapshot
, чтобы получить снимок текущего значения.
event_handler_invalid
Заголовок раздела «event_handler_invalid»%handler% should be a function. Did you mean to %suggestion%?
hydration_attribute_changed
Заголовок раздела «hydration_attribute_changed»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} />
hydration_html_changed
Заголовок раздела «hydration_html_changed»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_mismatch
Заголовок раздела «hydration_mismatch»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, который требует исправления.
invalid_raw_snippet_render
Заголовок раздела «invalid_raw_snippet_render»The `render` function passed to `createRawSnippet` should return HTML for a single element
legacy_recursive_reactive_block
Заголовок раздела «legacy_recursive_reactive_block»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`.
lifecycle_double_unmount
Заголовок раздела «lifecycle_double_unmount»Tried to unmount a component that was not mounted
ownership_invalid_binding
Заголовок раздела «ownership_invalid_binding»%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 />
).
ownership_invalid_mutation
Заголовок раздела «ownership_invalid_mutation»Mutating unbound props (`%name%`, at %location%) is strongly discouraged. Consider using `bind:%prop%={...}` in %parent% (or using a callback) instead
Рассмотрим следующий код:
<script> import Child from './Child.svelte'; let person = $state({ name: 'Florida', surname: 'Man' });</script>
<Child {person} />
<script> let { person } = $props();</script>
<input bind:value={person.name}><input bind:value={person.surname}>
<script lang="ts"> import Child from './Child.svelte'; let person = $state({ name: 'Florida', surname: 'Man' });</script>
<Child {person} />
<script lang="ts"> let { person } = $props();</script>
<input bind:value={person.name}><input bind:value={person.surname}>
Компонент Child
изменяет (мутирует
) объект person
, который принадлежит компоненту App
, без явного «разрешения» на это. Такой подход крайне не рекомендуется, так как может привести к коду, который сложно анализировать в больших масштабах («кто изменил это значение?»), отсюда и предупреждение.
Чтобы исправить это:
- Либо используйте callback-пропсы для передачи изменений
- Либо пометьте
person
как$bindable
select_multiple_invalid_value
Заголовок раздела «select_multiple_invalid_value»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
, чтобы сохранить текущий выбор
state_proxy_equality_mismatch
Заголовок раздела «state_proxy_equality_mismatch»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(...)
не создаёт прокси состояния.
transition_slide_display
Заголовок раздела «transition_slide_display»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
Общие предупреждения
Заголовок раздела «Общие предупреждения»dynamic_void_element_content
Заголовок раздела «dynamic_void_element_content»`<svelte:element this="%tag%">` is a void element — it cannot have content
Элементы типа <input>
не могут содержать дочерние элементы — любой переданный им контент будет проигнорирован.
state_snapshot_uncloneable
Заголовок раздела «state_snapshot_uncloneable»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);