SvelteKit это самая новая вещь на моём списке, и строительство фронтенда этого блога с ним является действительно первым реальным чем-то я доставил в нём.
До этого была большей частью React: приложение отслеживания фитнеса, проект хранилища стека MERN (Mongo, Express, React, Node, весь набор). Ментальная модель React, компоненты, props, состояние, повторные рендеры когда состояние изменяется, заняло у меня время чтобы действительно привыкнуть. SvelteKit просит тебя думать о почти ничём из того же способом, что было более дезориентирующим чем я ожидал входя.
первый баг был привычкой, не совсем ошибкой
Мой очень первый компонент Svelte был маленький список тегов для редактора постов администратора, добавь тег, посмотри как оно появляется в списке. Прямолинейное, я думал, приходя из React где я делал этот точный паттерн дюжину раз.
<script>
let tags = ["python", "learning"];
function addTag(newTag) {
tags.push(newTag);
}
</script>
<button on:click={() => addTag("sveltekit")}>add tag</button>
{#each tags as tag}
<span>{tag}</span>
{/each}Нажал кнопку. Ничего не произошло. Не ошибка, не предупреждение, список просто молчаливо не вырос, что как-то более запутанно чем крах был бы.
что я действительно гуглил
«svelte array push not updating ui» практически мгновенно привёл меня к точной проблеме, и это видимо достаточно распространено чтобы иметь своё собственное объяснение в документации Svelte что я явно не читал: реактивность Svelte срабатывает присвоением, не мутацией. tags.push(newTag) мутирует существующий массив на месте, и компилятор Svelte только знает переорендерить когда видит фактическое присвоение tags, что-то вроде tags = tags или совершенно новый массив. Push изменяет содержимое массива без какого-либо присвоения, так что ничто не говорит Svelte смотреть снова.
<script>
let tags = ["python", "learning"];
function addTag(newTag) {
tags = [...tags, newTag];
}
</script>Одна строка отличается. Развернуть старый массив в новый, присвоить его обратно tags, и теперь каждое добавление это реальное присвоение что Svelte видит. В React эта точная привычка имеет силу по соглашению (setTags([...tags, newTag]) это просто как useState работает, ты не можешь обойти его мутацией даже если хочешь), так что я никогда не должен был действительно думать о почему правило существует. Svelte позволил мне мутировать прямо, что ощущалось как меньше церемонии, прямо до того как это не работало и я не имел идеи почему.
нет виртуального DOM чтобы рассуждать о
Svelte компилирует твой компонент в код что прямо обновляет DOM когда значение изменяется. Нет шага виртуального DOM дёффинга чтобы мысленно моделировать, что звучит как должно сделать вещи проще и в основном делает, кроме я продолжал искать useState эквивалент что просто не существует в том же виде.
<script>
let count = 0;
</script>
<button on:click={() => count += 1}>
clicked {count} times
</button>Это весь компонент. Присвоение count это триггер реактивности. В React я достал бы useState из привычки и потом вспомнил: верно, просто переприсвои переменную, вот весь механизм здесь, и это то же правило что сломало мой список тегов, просто работает правильно на этот раз потому что += это присвоение.
действия формы были фактическим сюрпризом
Приходя из React и Express, где каждая отправка формы означает написание вызова fetch на клиенте и соответствующего маршрута API на сервере вручную, действия формы SvelteKit сначала меня спутали потому что они выглядели как они заменяют форму вместо просто обработки её отправки. Мой первый инстинкт был бороться с этим, и построить версию React вместо этого из привычки.
<!-- what I tried first, out of old habit -->
<script>
async function handleSubmit(event) {
event.preventDefault();
const formData = new FormData(event.target);
const res = await fetch("/api/comment", {
method: "POST",
body: JSON.stringify(Object.fromEntries(formData))
});
}
</script>
<form on:submit={handleSubmit}>
<textarea name="body"></textarea>
<button type="submit">Post comment</button>
</form>Технически работало. Но это означало написание маршрута API вручную чтобы получить этот fetch, парсинг JSON вручную, обработку ошибок вручную, всё то что я уже делал в Express, что SvelteKit на самом деле не нужно было мне делать себе.
// +page.server.ts, the SvelteKit way
export const actions = {
default: async ({ request }) => {
const data = await request.formData();
const body = data.get("body");
if (!body) {
return fail(400, { error: "comment cannot be empty" });
}
// save it
return { success: true };
}
};<form method="POST">
<textarea name="body"></textarea>
<button type="submit">Post comment</button>
</form>
Я отключил JavaScript в devtools из любопытства, на простой версии method="POST". Форма всё ещё работала. Вот когда это действительно щёлкнулось: действие это обработчик, и use:enhance это что-то что ты слоишь сверху позже чтобы сделать это ощущаться больше как одностраничное приложение, не что-то что форма нуждается функционировать вообще. Мой магазин MERN никогда не работал так. Убей JS на том приложении и это статическая страница с мёртвыми кнопками, потому что вся цепь отправки живёт полностью на клиенте.
реактивное утверждение что молчаливо использовало устаревшее значение
Утверждения $: это другой большой механизм реактивности Svelte, для значений вычисляемых из других значений, и я укусил один раз предполагая они переуправляются более агрессивно чем они на самом деле делают.
<script>
let posts = [];
let searchTerm = "";
$: filtered = posts.filter((p) => p.title.includes(searchTerm));
async function loadPosts() {
const res = await fetch("/api/posts");
posts = await res.json();
}
</script>Это выглядит нормально, и в основном это. Баг появился когда я рефакторил loadPosts мутировать существующий массив с posts.push(...newPosts) для кнопки «load more» вместо переприсвоения posts прямо, та же самая ошибка мутации-против-присвоения, просто в другом обличии на этот раз. filtered никогда не пересчитывался после «load more», потому что отслеживание зависимостей Svelte для $: смотрит на присвоение posts, то же правило везде, и мутированный массив никогда не срабатывает этим. Как только я признал форму ошибки из списка тегов раньше, исправление было то же самое одна строка: переприсвой вместо мутирования.
маршрутизация на основе файлов спотыкала меня примерно на день
Приходя из Express, где каждый маршрут это явная строка кода (app.get("/posts/:slug", ...)), маршрутизация на основе файлов SvelteKit, где папка названная [slug] в каталоге маршрутов просто становится динамическим маршрутом, чувствовалась как магия я не доверял первый день. Я продолжал ожидать найти «реальную» конфигурацию маршрутизатора где-то и был спутан что она не существует, что структура папок действительно весь стол маршрутов. Как только это щёлкнулось это ощущалось очевидным, тем же способом что правило переприсвоения делает, но тот день не доверять этому был реальным.
что всё ещё меня спотыкает
Реактивность через утверждения $: против простого переприсвоения не всегда очевидна мне, точно когда Svelte переуправляет что-то против когда это не делает, особенно как только зависимость зарыта внутри вызова функции нежели напрямую ссылается в утверждении. Я получил это неправильно по крайней мере дважды этот месяц способами что производили устаревшее значение молчаливо вместо ошибки, что более плохой режим отказа чем версия React «забыл это в массиве зависимостей». По крайней мере то обычно просто переуправляет слишком часто вместо слишком редко, что более прощающее направление быть неправильным.

где я фактически приземляюсь на это
Новая штука в списке, и также моя текущая любимая чтобы фактически писать день за днём, помимо ошибок мутации. Будь то фреймворк или просто медовый месяц чего угодно нового, спроси меня снова через шесть месяцев как только я ударю по чему-то что эквивалент SvelteKit реактивного-крючка головной боли окажется. Есть всегда один. Пока мой только та же самая уроку дважды, в списке тегов и потом в реактивном утверждении: изменяй значение, не просто меняй что оно указывает на месте.