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

Vue и Svelte — готовые компоненты

Готовые примеры Vue 3 и Svelte 5 с построчным разбором — как в галерее Turtle для рисования, только для кнопок, форм и списков в браузере.

Подойдёт, если вы:

  • ищете в Google «vue counter example», «svelte todo list», «v-model input», «vue fetch api onmounted»;
  • сдаёте лабораторную или курсовую по веб-интерфейсу;
  • уже знаете HTML + CSS или DOM без фреймворка и хотите реактивность без ручного textContent на каждой кнопке;
  • сравниваете Vue и Svelte перед выбором одного стека на pet-проект (рядом — React-рецепты).

Основы Vue и Svelte в браузере

Сначала теория

Пошаговый tutorial Vue — первая программа на Vue.js. Обзор фреймворка — Vue.js. React для сравнения — первая программа на React и готовые React-компоненты. Мобильный UI на Dart — Flutter и готовые виджеты. Запросы к серверу — fetch / axios и Fullstack. Каркас страницы — HTML + CSS. После сборки — Nginx под SPA.

Кому подойдёт эта страница

Школьникам — счётчик и список задач для урока информатики. Студентам — готовые блоки для лабораторной с разбором строк. Самоучкам — скопировали App.vuenpm run dev → разобрали таблицу под кодом. Ищете «как сделать счётчик на vue» или «svelte пример кнопки» — ниже код и объяснение, зачем каждая строка.


Частые запросы в Google — куда смотреть

Ищут в интернетеРаздел ниже
vue 3 counter example refСчётчик и имя — Vue
svelte 5 counter button onclickСчётчик и имя — Svelte
vue todo list v-for exampleСписок задач — Vue
svelte each block todoСписок задач — Svelte
vue fetch onmounted apiЗагрузка с API — Vue
svelte onmount fetch jsonЗагрузка с API — Svelte
vue defineprops defineemits childДочерний компонент — Vue
svelte component props callbackДочерний компонент — Svelte
npm create vue latest viteСоздание проекта
vue mustache not working raw {{ }}Частые ошибки

Базовые термины

ТерминПростыми словами
SPAОдна HTML-страница; при кликах контент меняет JavaScript, без полной перезагрузки
КомпонентФайл с разметкой + логикой + стилями (как отдельный «виджет»)
РеактивностьИзменили переменную в коде — экран обновился сам
ref (Vue)Реактивная «коробка»; в script меняют через .value
$state (Svelte 5)Реактивная переменная; присвоение count = 5 обновляет UI
v-model (Vue)Связь поля ввода и переменной в обе стороны
bind:value (Svelte)То же, что v-model для input
ViteDev-сервер и сборка; при сохранении файла страница обновляется (HMR)
mount('#app')«Включить» Vue/Svelte в <div id="app"> на странице

Зачем фреймворк, если есть DOM

На чистом DOM вы пишете так:

const btn = document.getElementById('plus');
const span = document.getElementById('count');
let n = 0;
btn.addEventListener('click', () => {
n += 1;
span.textContent = String(n);
});

Смысл: число n живёт в JavaScript, а textContent — ваша обязанность вручную синхронизировать экран. В списке из 20 полей таких строк становится десятки. Подробнее приёмы без фреймворка — 30 приёмов DOM.

Во Vue и Svelte вы описываете что показать, а фреймворк обновляет DOM при смене данных.


Как запустить пример за 2 минуты

  1. Установите Node.js LTS — в терминале node -v и npm -v показывают версии.
  2. Выполните команды из Создание проекта.
  3. Откройте src/App.vue (Vue) или src/routes/+page.svelte (SvelteKit).
  4. Удалите шаблонный код, вставьте пример целиком (включая <style>, если он в блоке).
  5. npm run dev — откройте ссылку из терминала (часто http://localhost:5173).
  6. Сохранили файл — страница обновилась (HMR).
ГдеПлюсы
Локально + ViteКак на работе: HMR, сборка dist/
StackBlitzБез установки Node
Vue SFC PlaygroundТолько .vue в браузере

Создание проекта

Vue 3 + Vite

Задача: получить пустой проект с горячей перезагрузкой.

node -v
npm -v
npm create vue@latest my-vue-lab
cd my-vue-lab
npm install
npm run dev

Разбор команд:

КомандаСмысл
node -vПроверка, что Node установлен
npm create vue@latest my-vue-labОфициальный генератор Vue 3 + Vite
cd my-vue-labВсе следующие команды — из папки проекта
npm installСкачать зависимости в node_modules/
npm run devDev-сервер; URL в терминале
Вопрос мастераНа лабораторную
TypeScriptможно No
Vue RouterYes, если нужны две «страницы»
PiniaNo на старте

Svelte 5 + SvelteKit

npm create svelte@latest my-svelte-lab
cd my-svelte-lab
npm install
npm run dev

Разбор: генератор создаёт SvelteKit; главный UI обычно в src/routes/+page.svelte.

Production: npm run build → папка dist/ или .svelte-kit/output (зависит от шаблона). Статика на хостинг — Nginx SPA.


Обязательный каркас Vue

Задача: понять три блока .vue и точку входа main.js.

src/main.js

import { createApp } from 'vue'
import App from './App.vue'

createApp(App).mount('#app')

Разбор построчно:

СтрокаСмысл
import &#123; createApp &#125; from 'vue'Фабрика приложения Vue
import App from './App.vue'Корневой компонент — ваш экран
createApp(App)Связать приложение с компонентом
.mount('#app')Отрисовать внутри <div id="app"> из index.html

Минимальный src/App.vue

<script setup>

import { ref } from 'vue'

const message = ref('Привет, Vue!')

</script>

<template>
<div class="app">
<h1>{{ message }}</h1>
</div>
</template>

<style scoped>
.app {
text-align: center;
padding: 2rem;
font-family: system-ui, sans-serif;
}
</style>

Разбор — script:

СтрокаСмысл
<script setup>Composition API: переменные видны в template
ref('…')Реактивная строка; в script — message.value
import &#123; ref &#125; from 'vue'Импорт из пакета vue

Разбор — template:

СтрокаСмысл
&#123;&#123; message &#125;&#125;Подстановка текста; без .value в шаблоне
<style scoped>CSS только для этого компонента

Что увидите: заголовок «Привет, Vue!» по центру.

Попробуйте: в script после объявления: message.value = 'Лабораторная №3'.


Обязательный каркас Svelte

<script>
let message = $state('Привет, Svelte!')
</script>

<main>
<h1>{message}</h1>
</main>

<style>
main {
text-align: center;
padding: 2rem;
font-family: system-ui, sans-serif;
}
</style>

Разбор построчно:

СтрокаСмысл
$state('…')Реактивность Svelte 5 (runes)
&#123;message&#125;Интерполяция — одни фигурные скобки, не двойные как во Vue
<style>Стили локальны для файла по умолчанию

Стартовые интерфейсы

Три задачи, которые чаще всего ищут: счётчик, список задач, загрузка JSON.


Счётчик и поле имени — Vue

Google: vue 3 counter ref example, vue v-model input greeting

Задача: кнопки ±1, сброс, приветствие по имени.

Вставьте в src/App.vue:

<script setup>

import { ref } from 'vue'

const count = ref(0)
const name = ref('')

const increment = () => { count.value++ }
const decrement = () => { count.value-- }
const reset = () => { count.value = 0 }

</script>

<template>
<div class="app">
<h1>Моя первая программа на Vue</h1>

<section class="greeting">
<input v-model="name" type="text" placeholder="Введите имя" />
<h2 v-if="name">Привет, {{ name }}!</h2>
</section>

<section class="counter">
<h2>Счётчик: {{ count }}</h2>
<button type="button" @click="decrement">−</button>
<button type="button" @click="reset">Сброс</button>
<button type="button" @click="increment">+</button>
</section>
</div>
</template>

<style scoped>
.app { text-align: center; padding: 2rem; }
.greeting, .counter { margin: 1.5rem 0; padding: 1rem; border-radius: 8px; }
.greeting { background: #f0f0f0; }
.counter { background: #e8f4f8; }
button {
margin: 0 4px;
padding: 0.5rem 1rem;
background: #42b883;
color: #fff;
border: none;
border-radius: 6px;
cursor: pointer;
}
button:hover { background: #3aa876; }
</style>

Разбор — script:

СтрокаСмысл
ref(0)Реактивное число, старт 0
count.value++В script у ref обязателен .value — главная ошибка новичков
() => { … }Функция передаётся в @click, не вызывается сразу

Разбор — template:

СинтаксисСмысл
v-model="name"Двусторонняя связь с input
v-if="name"Показать <h2>, только если строка не пустая
@click="increment"Сокращение v-on:click
type="button"Кнопка не отправляет форму

Что увидите: поле имени, приветствие при вводе, три кнопки счётчика.

Попробуйте: кнопка ×2 — const double = () => &#123; count.value *= 2 &#125; и @click="double".


Счётчик и поле имени — Svelte

<script>
let count = $state(0)
let name = $state('')

function increment() { count += 1 }
function decrement() { count -= 1 }
function reset() { count = 0 }
</script>

<main>
<h1>Моя первая программа на Svelte</h1>

<section class="greeting">
<input bind:value={name} type="text" placeholder="Введите имя" />
{#if name}
<h2>Привет, {name}!</h2>
{/if}
</section>

<section class="counter">
<h2>Счётчик: {count}</h2>
<button type="button" onclick={decrement}>−</button>
<button type="button" onclick={reset}>Сброс</button>
<button type="button" onclick={increment}>+</button>
</section>
</main>

<style>
main { text-align: center; padding: 2rem; }
.greeting, .counter { margin: 1.5rem 0; padding: 1rem; border-radius: 8px; }
.greeting { background: #f0f0f0; }
.counter { background: #fff3e0; }
button {
margin: 0 4px;
padding: 0.5rem 1rem;
background: #ff3e00;
color: #fff;
border: none;
border-radius: 6px;
cursor: pointer;
}
</style>

Сравнение с Vue:

ИдеяVueSvelte
Состояниеref(0) + .value$state(0) + count += 1
Inputv-model="name"bind:value=&#123;name&#125;
Условиеv-if="name"&#123;#if name&#125; … &#123;/if&#125;
Клик@click="fn"onclick=&#123;fn&#125;

Список задач — Vue

Google: vue todo list v-for v-model

<script setup>

import { ref } from 'vue'

const newTask = ref('')
const tasks = ref([
{ id: 1, text: 'Изучить ref и v-model', done: false },
])

let nextId = 2

const addTask = () => {
const text = newTask.value.trim()
if (!text) return
tasks.value.push({ id: nextId++, text, done: false })
newTask.value = ''
}

const removeTask = (id) => {
tasks.value = tasks.value.filter((t) => t.id !== id)
}

</script>

<template>
<section class="todo">
<h2>Список задач</h2>
<form @submit.prevent="addTask">
<input v-model="newTask" placeholder="Новая задача" />
<button type="submit">Добавить</button>
</form>
<ul>
<li v-for="task in tasks" :key="task.id">
<label>
<input type="checkbox" v-model="task.done" />
<span :class="{ done: task.done }">{{ task.text }}</span>
</label>
<button type="button" @click="removeTask(task.id)">×</button>
</li>
</ul>
</section>
</template>

<style scoped>
.done { text-decoration: line-through; color: #888; }
.todo ul { list-style: none; padding: 0; }
.todo li { display: flex; justify-content: space-between; align-items: center; margin: 0.5rem 0; }
</style>

Разбор построчно:

СтрокаСмысл
tasks = ref([...])Массив объектов в реактивной обёртке
trim() + if (!text) returnПустые задачи не добавляем
@submit.preventEnter в поле добавляет задачу без перезагрузки страницы
v-for + :key="task.id"Список; ключ нужен для корректного обновления DOM
v-model="task.done"Чекбокс меняет поле объекта в массиве
:class="&#123; done: task.done &#125;"Зачёркивание через CSS-класс
filter в removeTaskНовый массив без удалённой строки

Список задач — Svelte

<script>
let newTask = $state('')
let tasks = $state([
{ id: 1, text: 'Изучить $state и {#each}', done: false },
])
let nextId = 2

function addTask() {
const text = newTask.trim()
if (!text) return
tasks = [...tasks, { id: nextId++, text, done: false }]
newTask = ''
}

function removeTask(id) {
tasks = tasks.filter((t) => t.id !== id)
}
</script>

<section class="todo">
<h2>Список задач</h2>
<form onsubmit={(e) => { e.preventDefault(); addTask() }}>
<input bind:value={newTask} placeholder="Новая задача" />
<button type="submit">Добавить</button>
</form>
<ul>
{#each tasks as task (task.id)}
<li>
<label>
<input type="checkbox" bind:checked={task.done} />
<span class:done={task.done}>{task.text}</span>
</label>
<button type="button" onclick={() => removeTask(task.id)}>×</button>
</li>
{/each}
</ul>
</section>

<style>
.done { text-decoration: line-through; color: #888; }
.todo ul { list-style: none; padding: 0; }
.todo li { display: flex; justify-content: space-between; margin: 0.5rem 0; }
</style>

Разбор:

СтрокаСмысл
tasks = [...tasks, item]Новая ссылка на массив — явное обновление
&#123;#each tasks as task (task.id)&#125;Цикл; (task.id) — ключ, как :key во Vue
class:done=&#123;task.done&#125;Условный класс в Svelte

Загрузка с API — Vue

Google: vue 3 fetch onmounted example

Задача: при открытии страницы запросить JSON и показать список или ошибку.

Для теста без своего сервера — публичный API. Для курсовой с Node — API «Заметки» и Fullstack 264.

<script setup>

import { ref, onMounted } from 'vue'

const notes = ref([])
const error = ref('')
const loading = ref(true)

onMounted(async () => {
try {
const res = await fetch('https://jsonplaceholder.typicode.com/posts?_limit=5')
if (!res.ok) throw new Error(`HTTP ${res.status}`)
notes.value = await res.json()
} catch (e) {
error.value = e.message
} finally {
loading.value = false
}
})

</script>

<template>
<section>
<h2>Записи с сервера</h2>
<p v-if="loading">Загрузка…</p>
<p v-else-if="error">Ошибка: {{ error }}</p>
<ul v-else>
<li v-for="n in notes" :key="n.id">{{ n.title }}</li>
</ul>
</section>
</template>

Разбор построчно:

СтрокаСмысл
onMountedКод после появления компонента в DOM
await fetchGET по умолчанию
!res.ok404/500 — fetch не бросает ошибку сам
notes.value = await res.json()Массив в реактивное состояние
finallyСнять «Загрузка…» всегда
v-if / v-else-if / v-elseТри экрана: ждём, ошибка, данные

Шаблоны fetch1145.


Загрузка с API — Svelte

<script>
import { onMount } from 'svelte'

let notes = $state([])
let error = $state('')
let loading = $state(true)

onMount(async () => {
try {
const res = await fetch('https://jsonplaceholder.typicode.com/posts?_limit=5')
if (!res.ok) throw new Error(`HTTP ${res.status}`)
notes = await res.json()
} catch (e) {
error = e.message
} finally {
loading = false
}
})
</script>

<section>
<h2>Записи с сервера</h2>
{#if loading}
<p>Загрузка…</p>
{:else if error}
<p>Ошибка: {error}</p>
{:else}
<ul>
{#each notes as n (n.id)}
<li>{n.title}</li>
{/each}
</ul>
{/if}
</section>

Разбор: onMount из svelte — аналог onMounted. &#123;:else if&#125; — цепочка условий.


Дочерний компонент — Vue

Google: vue defineprops defineemits example

Задача: вынести счётчик в файл; родитель хранит число, ребёнок шлёт события.

src/components/Counter.vue:

<script setup>
defineProps({
value: { type: Number, required: true },
})
const emit = defineEmits(['increment', 'decrement', 'reset'])
</script>

<template>
<section class="counter">
<h2>Счётчик: {{ value }}</h2>
<button type="button" @click="emit('decrement')">−</button>
<button type="button" @click="emit('reset')">Сброс</button>
<button type="button" @click="emit('increment')">+</button>
</section>
</template>

src/App.vue:

<script setup>

import { ref } from 'vue'
import Counter from './components/Counter.vue'

const count = ref(0)

</script>

<template>
<Counter
:value="count"
@increment="count++"
@decrement="count--"
@reset="count = 0"
/>
</template>

Разбор:

МестоСмысл
definePropsДанные вниз от родителя
defineEmitsСобытия вверх
:value="count"Привязка prop (v-bind:value)
@increment="count++"Родитель — единственный источник истины

Тот же паттерн в React — 1146.


Дочерний компонент — Svelte

src/lib/Counter.svelte:

<script>
let { value = 0, onincrement, ondecrement, onreset } = $props()
</script>

<section class="counter">
<h2>Счётчик: {value}</h2>
<button type="button" onclick={ondecrement}>−</button>
<button type="button" onclick={onreset}>Сброс</button>
<button type="button" onclick={onincrement}>+</button>
</section>

Родитель:

<script>
import Counter from './lib/Counter.svelte'
let count = $state(0)
</script>

<Counter
value={count}
onincrement={() => count++}
ondecrement={() => count--}
onreset={() => { count = 0 }}
/>

Разбор: в Svelte 5 колбэки onincrement вместо emit из Svelte 4.


Форма с проверкой — Vue

<script setup>

import { ref, computed } from 'vue'

const email = ref('')
const submitted = ref(false)

const emailError = computed(() => {
if (!submitted.value) return ''
if (!email.value.includes('@')) return 'Нужен символ @'
return ''
})

const onSubmit = () => {
submitted.value = true
if (!emailError.value) alert(`Отправлено: ${email.value}`)
}

</script>

<template>
<form @submit.prevent="onSubmit">
<label>
Email
<input v-model="email" type="email" />
</label>
<p v-if="emailError" class="err">{{ emailError }}</p>
<button type="submit">Отправить</button>
</form>
</template>

<style scoped>
.err { color: #c00; font-size: 0.9rem; }
</style>

Разбор:

СтрокаСмысл
submittedОшибку показываем только после первой отправки
computedПересчёт при смене email или submitted
emailError.value в onSubmitУ computed в script тоже .value

Vue и Svelte — что выбрать на курсовой

КритерийVue 3Svelte 5
СинтаксисHTML + v-*HTML + {#…}
После HTML/CSSОбычно привычнееМягкий вход
ВакансииМногоМеньше, растёт
Учебник282экосистема JS

Один фреймворк на проект до рабочего pet-приложения — достаточно для зачёта.


Частые ошибки

СимптомПричинаРешение
Счётчик не меняется (Vue)count++ без .valuecount.value++ в script
Сырой &#123;&#123; count &#125;&#125; на страницеНет mount('#app')Проверить main.js, консоль F12
Failed to fetchCORS или API выключен264, прокси Vite
Предупреждение про keyНет :key в v-fortask.id
Svelte: список «застыл»Мутация без новой ссылкиtasks = [...tasks, x]
404 на /about после buildSPA без fallbackNginx SPA
Один UI-стек в репозитории

На старте достаточно Vue или Svelte (или React — отдельная статья). Три фреймворка в одной папке ломают сборку. Сравнение синтаксиса — здесь; курсовой проект — в одном выбранном стеке.


Практика — что добавить для зачёта

УсложнениеVueSvelte
Фильтр задачcomputed$derived
ТаймерonMounted + onUnmountedonMount + cleanup
POST на APIfetch + method: 'POST'то же
Две страницыVue RouterSvelteKit routes

Чек-лист перед сдачей лабораторной

  • npm run dev без ошибок, UI виден в браузере.
  • В Vue в script у ref при изменении есть .value.
  • У v-for / &#123;#each&#125; есть ключ.
  • fetch проверяет res.ok.
  • npm run build проходит успешно.

Куда двигаться дальше

ЗадачаМатериал
Tutorial Vue282
React-рецепты1146
DOM без фреймворка1144
fetch, axios1145
Turtle / p5111 · 1114

См. также

Другие статьи этого же раздела в боковом меню (как на странице "О разделе").