Skip to content

Instantly share code, notes, and snippets.

@dmitry-osin
Last active April 28, 2025 23:05
Show Gist options
  • Save dmitry-osin/00aaa05a9bab5e0c92f4649a708e2775 to your computer and use it in GitHub Desktop.
Save dmitry-osin/00aaa05a9bab5e0c92f4649a708e2775 to your computer and use it in GitHub Desktop.
Шпаргалка по Vue.js 3

Шпаргалка по Vue.js 3

Содержание

1. Основы Vue.js 3

Что такое Vue.js?

Vue.js — это прогрессивный JavaScript-фреймворк для создания пользовательских интерфейсов. В отличие от монолитных фреймворков, Vue спроектирован с возможностью постепенного внедрения.

Ключевые особенности Vue.js 3:

  • Реактивность — автоматическое обновление DOM при изменении данных
  • Декларативный рендеринг — описание HTML с использованием шаблонов
  • Компонентная архитектура — разделение UI на независимые компоненты
  • Composition API — новый подход к организации логики компонентов
  • Virtual DOM — эффективное обновление DOM

2. Установка и настройка

Установка Vue CLI:

npm install -g @vue/cli

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

vue create my-project

Структура проекта Vue.js 3:

my-project/
├── node_modules/
├── public/
│   ├── favicon.ico
│   └── index.html
├── src/
│   ├── assets/
│   ├── components/
│   ├── App.vue
│   └── main.js
├── package.json
└── vue.config.js

Базовый файл main.js:

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

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

3. Composition API

Composition API — это новый способ организации логики компонентов в Vue.js 3.

Основные функции:

ref

Создает реактивную ссылку на примитивное значение:

import { ref } from 'vue'

const count = ref(0)
// Доступ к значению через .value
console.log(count.value) // 0
count.value++
console.log(count.value) // 1

reactive

Создает реактивный объект:

import { reactive } from 'vue'

const state = reactive({
  count: 0,
  name: 'Vue'
})
// Доступ напрямую (без .value)
console.log(state.count) // 0
state.count++

computed

Создает вычисляемое свойство:

import { ref, computed } from 'vue'

const count = ref(0)
const doubleCount = computed(() => count.value * 2)

watch и watchEffect

Отслеживание изменений:

import { ref, watch, watchEffect } from 'vue'

const count = ref(0)

// Явное отслеживание
watch(count, (newValue, oldValue) => {
  console.log(`count изменился с ${oldValue} на ${newValue}`)
})

// Автоматическое отслеживание зависимостей
watchEffect(() => {
  console.log(`count сейчас: ${count.value}`)
})

lifecycle hooks

Хуки жизненного цикла:

import { onMounted, onUpdated, onUnmounted } from 'vue'

export default {
  setup() {
    onMounted(() => {
      console.log('Компонент смонтирован')
    })
    
    onUpdated(() => {
      console.log('Компонент обновлен')
    })
    
    onUnmounted(() => {
      console.log('Компонент удален')
    })
  }
}

provide/inject

Передача данных между компонентами:

// Родительский компонент
import { provide, ref } from 'vue'

export default {
  setup() {
    const theme = ref('dark')
    provide('theme', theme)
  }
}

// Дочерний компонент
import { inject } from 'vue'

export default {
  setup() {
    const theme = inject('theme')
    return { theme }
  }
}

4. Options API

Options API — традиционный способ создания компонентов в Vue.

Основные опции:

data

export default {
  data() {
    return {
      count: 0,
      message: 'Hello Vue!'
    }
  }
}

methods

export default {
  data() {
    return { count: 0 }
  },
  methods: {
    increment() {
      this.count++
    }
  }
}

computed

export default {
  data() {
    return { count: 0 }
  },
  computed: {
    doubleCount() {
      return this.count * 2
    }
  }
}

watch

export default {
  data() {
    return { count: 0 }
  },
  watch: {
    count(newValue, oldValue) {
      console.log(`count изменился с ${oldValue} на ${newValue}`)
    }
  }
}

5. Компоненты

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

<template>
  <div>
    <h1>{{ title }}</h1>
    <button @click="increment">Счетчик: {{ count }}</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      title: 'Мой компонент',
      count: 0
    }
  },
  methods: {
    increment() {
      this.count++
    }
  }
}
</script>

<style scoped>
h1 {
  color: blue;
}
</style>

Использование Composition API в SFC

<template>
  <div>
    <h1>{{ title }}</h1>
    <button @click="increment">Счетчик: {{ count }}</button>
  </div>
</template>

<script setup>
import { ref } from 'vue'

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

function increment() {
  count.value++
}
</script>

<style scoped>
h1 {
  color: blue;
}
</style>

Регистрация компонентов

Глобальная регистрация:

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

const app = createApp(App)
app.component('MyComponent', MyComponent)
app.mount('#app')

Локальная регистрация:

import MyComponent from './components/MyComponent.vue'

export default {
  components: {
    MyComponent
  }
}

Передача данных между компонентами

Props (сверху вниз):

<!-- Родительский компонент -->
<template>
  <ChildComponent :message="parentMessage" />
</template>

<script setup>
import { ref } from 'vue'
import ChildComponent from './ChildComponent.vue'

const parentMessage = ref('Сообщение от родителя')
</script>

<!-- Дочерний компонент (ChildComponent.vue) -->
<template>
  <div>{{ message }}</div>
</template>

<script setup>
const props = defineProps({
  message: {
    type: String,
    required: true
  }
})
</script>

События (снизу вверх):

<!-- Дочерний компонент -->
<template>
  <button @click="emitEvent">Отправить событие</button>
</template>

<script setup>
const emit = defineEmits(['custom-event'])

function emitEvent() {
  emit('custom-event', 'Данные события')
}
</script>

<!-- Родительский компонент -->
<template>
  <ChildComponent @custom-event="handleEvent" />
</template>

<script setup>
import ChildComponent from './ChildComponent.vue'

function handleEvent(data) {
  console.log('Получено событие с данными:', data)
}
</script>

6. Директивы

v-bind

Связывает атрибут с выражением:

<img v-bind:src="imageUrl">
<!-- Сокращенная запись -->
<img :src="imageUrl">

v-on

Привязывает обработчик события:

<button v-on:click="handleClick">Нажми меня</button>
<!-- Сокращенная запись -->
<button @click="handleClick">Нажми меня</button>

v-model

Двустороннее связывание:

<input v-model="message">

v-if, v-else-if, v-else

Условный рендеринг:

<div v-if="type === 'A'">A</div>
<div v-else-if="type === 'B'">B</div>
<div v-else>Не A/B</div>

v-show

Условное отображение (через CSS):

<div v-show="isVisible">Видимый элемент</div>

v-for

Рендеринг списков:

<ul>
  <li v-for="(item, index) in items" :key="item.id">
    {{ index }}: {{ item.name }}
  </li>
</ul>

v-slot

Слоты для контента:

<!-- Базовый слот -->
<template v-slot:default>
  Содержимое слота
</template>

<!-- Именованный слот (сокращенно) -->
<template #header>
  Заголовок
</template>

Пользовательские директивы

// Глобальная регистрация
app.directive('focus', {
  mounted(el) {
    el.focus()
  }
})

// Использование
<input v-focus>

7. Маршрутизация (Vue Router)

Установка:

npm install vue-router@4

Настройка:

import { createRouter, createWebHistory } from 'vue-router'
import Home from './views/Home.vue'
import About from './views/About.vue'

const routes = [
  { path: '/', component: Home },
  { path: '/about', component: About },
  { 
    path: '/user/:id', 
    component: User,
    props: true 
  }
]

const router = createRouter({
  history: createWebHistory(),
  routes
})

// Подключение к приложению
import { createApp } from 'vue'
import App from './App.vue'

const app = createApp(App)
app.use(router)
app.mount('#app')

Использование в компоненте:

<template>
  <div>
    <router-link to="/">Главная</router-link>
    <router-link to="/about">О нас</router-link>
    
    <!-- Отображение активного маршрута -->
    <router-view></router-view>
  </div>
</template>

<script setup>
import { useRouter, useRoute } from 'vue-router'

const router = useRouter()
const route = useRoute()

// Программная навигация
function goToHome() {
  router.push('/')
}

// Доступ к параметрам маршрута
console.log(route.params.id)
</script>

Защита маршрутов:

router.beforeEach((to, from, next) => {
  if (to.meta.requiresAuth && !isAuthenticated) {
    next('/login')
  } else {
    next()
  }
})

8. Управление состоянием

Pinia (рекомендуемый подход)

Установка:

npm install pinia

Настройка:

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

const pinia = createPinia()
const app = createApp(App)

app.use(pinia)
app.mount('#app')

Создание хранилища:

// stores/counter.js
import { defineStore } from 'pinia'

export const useCounterStore = defineStore('counter', {
  // state
  state: () => ({
    count: 0
  }),
  // getters
  getters: {
    doubleCount: (state) => state.count * 2
  },
  // actions
  actions: {
    increment() {
      this.count++
    }
  }
})

// Вариант с Composition API
export const useCounterStore = defineStore('counter', () => {
  const count = ref(0)
  const doubleCount = computed(() => count.value * 2)
  
  function increment() {
    count.value++
  }
  
  return { count, doubleCount, increment }
})

Использование в компоненте:

<template>
  <div>
    <p>Счетчик: {{ counterStore.count }}</p>
    <p>Удвоенный: {{ counterStore.doubleCount }}</p>
    <button @click="counterStore.increment">Увеличить</button>
  </div>
</template>

<script setup>
import { useCounterStore } from '@/stores/counter'

const counterStore = useCounterStore()
</script>

Vuex (альтернатива)

Установка:

npm install vuex@next

Настройка:

import { createStore } from 'vuex'

const store = createStore({
  state() {
    return {
      count: 0
    }
  },
  mutations: {
    increment(state) {
      state.count++
    }
  },
  actions: {
    incrementAsync({ commit }) {
      setTimeout(() => {
        commit('increment')
      }, 1000)
    }
  },
  getters: {
    doubleCount: (state) => state.count * 2
  }
})

// Подключение к приложению
import { createApp } from 'vue'
import App from './App.vue'

const app = createApp(App)
app.use(store)
app.mount('#app')

9. Жизненный цикл компонентов

Хуки жизненного цикла (Options API):

export default {
  beforeCreate() {
    // Перед инициализацией
  },
  created() {
    // Компонент инициализирован
  },
  beforeMount() {
    // Перед монтированием DOM
  },
  mounted() {
    // DOM смонтирован
  },
  beforeUpdate() {
    // Перед обновлением DOM
  },
  updated() {
    // После обновления DOM
  },
  beforeUnmount() {
    // Перед удалением компонента
  },
  unmounted() {
    // Компонент удален
  }
}

Хуки жизненного цикла (Composition API):

import {
  onBeforeMount,
  onMounted,
  onBeforeUpdate,
  onUpdated,
  onBeforeUnmount,
  onUnmounted
} from 'vue'

export default {
  setup() {
    onBeforeMount(() => {
      // Перед монтированием DOM
    })
    
    onMounted(() => {
      // DOM смонтирован
    })
    
    onBeforeUpdate(() => {
      // Перед обновлением DOM
    })
    
    onUpdated(() => {
      // После обновления DOM
    })
    
    onBeforeUnmount(() => {
      // Перед удалением компонента
    })
    
    onUnmounted(() => {
      // Компонент удален
    })
  }
}

10. Работа с формами

Базовое использование v-model:

<template>
  <form @submit.prevent="submitForm">
    <div>
      <label for="name">Имя:</label>
      <input id="name" v-model="form.name">
    </div>
    
    <div>
      <label for="email">Email:</label>
      <input id="email" v-model="form.email" type="email">
    </div>
    
    <div>
      <label>Пол:</label>
      <input type="radio" v-model="form.gender" value="male"> Мужской
      <input type="radio" v-model="form.gender" value="female"> Женский
    </div>
    
    <div>
      <label>Интересы:</label>
      <input type="checkbox" v-model="form.interests" value="sports"> Спорт
      <input type="checkbox" v-model="form.interests" value="music"> Музыка
      <input type="checkbox" v-model="form.interests" value="reading"> Чтение
    </div>
    
    <div>
      <label for="country">Страна:</label>
      <select id="country" v-model="form.country">
        <option value="">Выберите страну</option>
        <option value="ru">Россия</option>
        <option value="us">США</option>
        <option value="uk">Великобритания</option>
      </select>
    </div>
    
    <div>
      <label for="message">Сообщение:</label>
      <textarea id="message" v-model="form.message"></textarea>
    </div>
    
    <button type="submit">Отправить</button>
  </form>
</template>

<script setup>
import { reactive } from 'vue'

const form = reactive({
  name: '',
  email: '',
  gender: '',
  interests: [],
  country: '',
  message: ''
})

function submitForm() {
  console.log('Форма отправлена:', form)
  // Здесь логика отправки формы
}
</script>

Валидация форм:

<template>
  <form @submit.prevent="submitForm">
    <div>
      <label for="name">Имя:</label>
      <input id="name" v-model="form.name" @blur="validateName">
      <span v-if="errors.name" class="error">{{ errors.name }}</span>
    </div>
    
    <div>
      <label for="email">Email:</label>
      <input id="email" v-model="form.email" type="email" @blur="validateEmail">
      <span v-if="errors.email" class="error">{{ errors.email }}</span>
    </div>
    
    <button type="submit" :disabled="!isFormValid">Отправить</button>
  </form>
</template>

<script setup>
import { reactive, computed } from 'vue'

const form = reactive({
  name: '',
  email: ''
})

const errors = reactive({
  name: '',
  email: ''
})

function validateName() {
  if (!form.name) {
    errors.name = 'Имя обязательно для заполнения'
  } else if (form.name.length < 3) {
    errors.name = 'Имя должно содержать не менее 3 символов'
  } else {
    errors.name = ''
  }
}

function validateEmail() {
  const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
  if (!form.email) {
    errors.email = 'Email обязателен для заполнения'
  } else if (!emailRegex.test(form.email)) {
    errors.email = 'Введите корректный email'
  } else {
    errors.email = ''
  }
}

const isFormValid = computed(() => {
  return form.name && form.email && !errors.name && !errors.email
})

function submitForm() {
  validateName()
  validateEmail()
  
  if (isFormValid.value) {
    console.log('Форма отправлена:', form)
    // Здесь логика отправки формы
  }
}
</script>

<style scoped>
.error {
  color: red;
  font-size: 0.8em;
}
</style>

11. Анимации и переходы

Базовые переходы:

<template>
  <button @click="show = !show">Переключить</button>
  
  <Transition name="fade">
    <p v-if="show">Привет, мир!</p>
  </Transition>
</template>

<script setup>
import { ref } from 'vue'

const show = ref(true)
</script>

<style>
.fade-enter-active,
.fade-leave-active {
  transition: opacity 0.5s ease;
}

.fade-enter-from,
.fade-leave-to {
  opacity: 0;
}
</style>

Анимация списков:

<template>
  <button @click="addItem">Добавить</button>
  <button @click="removeItem">Удалить</button>
  
  <TransitionGroup name="list" tag="ul">
    <li v-for="item in items" :key="item.id">
      {{ item.text }}
    </li>
  </TransitionGroup>
</template>

<script setup>
import { ref } from 'vue'

const items = ref([
  { id: 1, text: 'Элемент 1' },
  { id: 2, text: 'Элемент 2' },
  { id: 3, text: 'Элемент 3' }
])

let nextId = 4

function addItem() {
  items.value.push({
    id: nextId++,
    text: `Элемент ${nextId - 1}`
  })
}

function removeItem() {
  if (items.value.length > 0) {
    items.value.splice(Math.floor(Math.random() * items.value.length), 1)
  }
}
</script>

<style>
.list-enter-active,
.list-leave-active {
  transition: all 0.5s ease;
}

.list-enter-from,
.list-leave-to {
  opacity: 0;
  transform: translateX(30px);
}

.list-move {
  transition: transform 0.5s ease;
}
</style>

12. Тестирование

Установка инструментов тестирования:

npm install --save-dev @vue/test-utils vitest

Модульное тестирование компонента:

// Counter.spec.js
import { mount } from '@vue/test-utils'
import { describe, it, expect } from 'vitest'
import Counter from '../Counter.vue'

describe('Counter.vue', () => {
  it('увеличивает счетчик при нажатии на кнопку', async () => {
    const wrapper = mount(Counter)
    
    // Проверяем начальное состояние
    expect(wrapper.text()).toContain('Счетчик: 0')
    
    // Нажимаем на кнопку
    await wrapper.find('button').trigger('click')
    
    // Проверяем, что счетчик увеличился
    expect(wrapper.text()).toContain('Счетчик: 1')
  })
})

Тестирование хука жизненного цикла:

import { mount } from '@vue/test-utils'
import { describe, it, expect, vi } from 'vitest'
import LifecycleComponent from '../LifecycleComponent.vue'

describe('LifecycleComponent.vue', () => {
  it('вызывает метод при монтировании', () => {
    // Создаем шпиона для console.log
    const spy = vi.spyOn(console, 'log')
    
    // Монтируем компонент
    mount(LifecycleComponent)
    
    // Проверяем, что console.log был вызван с ожидаемым сообщением
    expect(spy).toHaveBeenCalledWith('Компонент смонтирован')
    
    // Восстанавливаем оригинальную функцию
    spy.mockRestore()
  })
})

13. Полезные ресурсы

Официальная документация:

Учебные ресурсы:

Инструменты:

  • Vue DevTools
  • Vite - быстрый инструмент сборки
  • Nuxt.js - фреймворк для создания SSR приложений на Vue

Экосистема Vue.js:

  • Vueuse - коллекция утилит для Composition API
  • Vue Test Utils - утилиты для тестирования
  • Quasar - фреймворк UI компонентов
  • Vuetify - Material Design компоненты
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment