← Курс/Введение в Vue 3#200 из 257+30 XP

Введение в Vue 3

Что такое Vue.js

Vue.js — это **прогрессивный JavaScript-фреймворк** для создания пользовательских интерфейсов. Он называется прогрессивным, потому что его можно внедрять постепенно: начать с подключения через CDN для одной страницы и дорасти до полноценного SPA (Single Page Application).

Vue 3 вышел в 2020 году и принёс Composition API, значительно улучшенную TypeScript-поддержку и более производительный рантайм.

Реактивность — сердце Vue

Главная идея Vue — **реактивность**: данные связаны с DOM, и при изменении данных DOM обновляется автоматически.

В ванильном JS вам нужно делать это вручную:

// Vanilla JS — всё вручную
let count = 0
const button = document.getElementById('btn')
const display = document.getElementById('count')

button.addEventListener('click', () => {
  count++
  display.textContent = count  // нужно вручную обновить DOM
})

В Vue это происходит автоматически:

<!-- Vue — данные и DOM синхронизированы автоматически -->
<template>
  <button @click="count++">Нажми меня</button>
  <p>Счётчик: {{ count }}</p>
</template>

<script setup>
import { ref } from 'vue'
const count = ref(0)
</script>

Виртуальный DOM

Vue использует **виртуальный DOM (VDOM)** — легковесное JavaScript-представление настоящего DOM-дерева. Когда данные меняются, Vue:

1. Создаёт новый виртуальный DOM

2. Сравнивает его со старым (алгоритм **diffing**)

3. Применяет только необходимые изменения к реальному DOM (процесс **patching**)

Это значительно эффективнее, чем пересоздавать весь DOM при каждом изменении.

Однофайловые компоненты (SFC)

Vue-приложения состоят из компонентов. Каждый компонент — это .vue файл с тремя секциями:

<!-- MyComponent.vue -->
<template>
  <!-- HTML-разметка компонента -->
  <div class="card">
    <h2>{{ title }}</h2>
    <p v-if="isVisible">Видимый текст</p>
    <button @click="handleClick">Кнопка</button>
  </div>
</template>

<script setup>
// JavaScript логика (Composition API)
import { ref } from 'vue'

const title = ref('Мой компонент')
const isVisible = ref(true)

function handleClick() {
  isVisible.value = !isVisible.value
}
</script>

<style scoped>
/* CSS стили (scoped — только для этого компонента) */
.card {
  padding: 16px;
  border: 1px solid #ccc;
}
</style>

Подключение через CDN

Начать работу с Vue можно без сборщиков — просто подключив CDN:

<!DOCTYPE html>
<html>
<head>
  <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
</head>
<body>
  <div id="app">
    <p>{{ message }}</p>
    <button @click="message = 'Привет, Vue!'">Изменить</button>
  </div>

  <script>
    const { createApp, ref } = Vue

    createApp({
      setup() {
        const message = ref('Привет, мир!')
        return { message }
      }
    }).mount('#app')
  </script>
</body>
</html>

Как Vue отслеживает изменения

В основе реактивности Vue лежит JavaScript **Proxy** — объект-обёртка, который перехватывает операции чтения и записи свойств. Когда вы читаете свойство реактивного объекта, Vue запоминает зависимость. Когда пишете — Vue знает, что нужно обновить все зависящие части интерфейса.

Примеры

Эмуляция реактивности через Proxy — как Vue отслеживает изменения "под капотом"

// Vue использует Proxy для отслеживания изменений в данных.
// Разберём как это работает на упрощённом примере.

// Хранилище текущего эффекта (функции, которая "следит" за данными)
let activeEffect = null

// Хранилище зависимостей: { объект -> { свойство -> Set эффектов } }
const deps = new WeakMap()

// Получить набор зависимостей для конкретного объекта и свойства
function getDep(target, key) {
  if (!deps.has(target)) {
    deps.set(target, new Map())
  }
  const targetDeps = deps.get(target)
  if (!targetDeps.has(key)) {
    targetDeps.set(key, new Set())
  }
  return targetDeps.get(key)
}

// Создать реактивный объект через Proxy
function reactive(obj) {
  return new Proxy(obj, {
    get(target, key) {
      // Если есть активный эффект — запоминаем зависимость
      if (activeEffect) {
        const dep = getDep(target, key)
        dep.add(activeEffect)
        console.log(`  [get] ${String(key)} — подписан эффект`)
      }
      return target[key]
    },
    set(target, key, value) {
      const oldValue = target[key]
      target[key] = value
      console.log(`[set] ${String(key)}: ${oldValue} -> ${value}`)
      // Уведомляем все зависящие эффекты
      const dep = getDep(target, key)
      dep.forEach(effect => effect())
      return true
    }
  })
}

// Запустить эффект и отследить зависимости
function watchEffect(fn) {
  activeEffect = fn
  fn()  // первый запуск — собираем зависимости
  activeEffect = null
}

// --- Демонстрация ---

const state = reactive({ name: 'Алексей', age: 25 })

console.log('=== Регистрируем эффект ===')
watchEffect(() => {
  // Эта функция зависит от state.name и state.age
  console.log(`Пользователь: ${state.name}, возраст: ${state.age}`)
})

console.log('\n=== Меняем name ===')
state.name = 'Борис'
// Эффект автоматически перезапустится

console.log('\n=== Меняем age ===')
state.age = 30
// Эффект снова перезапустится

console.log('\nИтого: при каждом изменении данных Vue знает')
console.log('какие части интерфейса нужно перерендерить.')