Vue.js: подробное руководство по фреймворку

Раздел: Frontend -> Фреймворки/библиотеки

Основы работы с Vue.js

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

Как инициализировать приложение Vue?

Наиболее простой способ - использовать Options API через глобальную функцию createApp. Пример:

const app = Vue.createApp({
  data() {
    return { message: 'Привет, Vue!' }
  },
  template: '<h1>{{ message }}</h1>'
});
app.mount('#app');

Next js (next.js)

Приложение монтируется в элемент с идентификатором app. Внутри данных объявлено свойство message, которое реактивно отображается в шаблоне.

Альтернативный подход - Composition API с функцией setup. Он даёт больше гибкости и лучше подходит для сложной логики.

Vue.createApp({
  setup() {
    const count = Vue.ref(0);
    const increment = () => { count.value++ };
    return { count, increment };
  },
  template: '<button @click="increment">{{ count }}</button>'
}).mount('#app');

Vue js (vue js)

Здесь используется ref для реактивного значения. Доступ к нему осуществляется через .value в JavaScript, но в шаблоне он распаковывается автоматически.

Типичная ошибка: забыть вызвать mount() - приложение не отобразится. Также в Composition API новички часто путают ref и reactive: ref подходит для примитивов, reactive - для объектов.

Как выполнять условный рендеринг?

Директивы v-if, v-else-if, v-else полностью удаляют/добавляют элемент из DOM.

<p v-if="score >= 90">Отлично</p>
<p v-else-if="score >= 70">Хорошо</p>
<p v-else>Попробуй ещё</p>

v-show только переключает CSS-свойство display, не удаляя элемент. Используется для частых переключений.

<div v-show="isVisible">Видимый блок</div>

Частая проблема: применение v-if и v-for на одном элементе - Vue запрещает это в одной директиве. В таких случаях нужно обернуть v-if в <template>.

Как отображать списки данных?

Директива v-for перебирает массив или объект. Обязательно указывать атрибут :key для идентификации элементов.

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

Можно получать индекс или ключ объекта:

<div v-for="(value, key, index) in object" :key="key">
  {{ index }}. {{ key }}: {{ value }}
</div>

Типичная ошибка: использование :key с индексом массива при динамическом изменении порядка - это приводит к неверному обновлению DOM. Лучше применять уникальный идентификатор.

Как реагировать на изменения данных?

Свойство watch позволяет выполнять побочные действия при изменении отслеживаемого значения.

watch: {
  question(newQ, oldQ) {
    this.fetchAnswer(newQ);
  }
}

В Composition API используется функция watch:

watch(() => question.value, (newVal, oldVal) => {
  console.log('Вопрос изменился');
});

Вычисляемые свойства computed возвращают производное значение, которое кешируется до изменения зависимостей.

computed: {
  reversedMessage() {
    return this.message.split('').reverse().join('');
  }
}

Проблема: внутри watch нельзя изменять отслеживаемое значение напрямую - это вызовет бесконечный цикл. Вместо этого следует использовать computed или дополнительную переменную.

Как обрабатывать события?

Директива v-on (сокращённо @) привязывает обработчик.

<button @click="increment">+1</button>

Метод increment определяется в объекте methods:

methods: {
  increment(event) {
    this.count++;
  }
}

Также можно передать собственные аргументы и сам объект события через $event:

<button @click="handler('param', $event)">Нажми</button>

Ошибка: использование стрелочной функции в methods (increment: () => { ... }) приводит к потере контекста this. Методы должны быть обычными функциями.

Как работать с формами?

Директива v-model обеспечивает двустороннее связывание для элементов форм.

<input v-model="username" placeholder="Введите имя" />
<p>Привет, {{ username }}!</p>

По умолчанию v-model реагирует на событие input. Для textarea, select работает аналогично.

Модификаторы .lazy, .number, .trim меняют поведение:

<input v-model.lazy="comment" />  <!-- синхронизация по blur -->
<input v-model.number="age" />  <!-- преобразует в число -->
<input v-model.trim="name" />   <!-- удаляет пробелы -->

Проблема: использование v-model на кастомном компоненте требует правильной реализации модели (prop modelValue и событие update:modelValue). Без этого двустороннее связывание не работает.

Как создавать компоненты?

Глобальная регистрация компонента через app.component:

app.component('my-button', {
  template: '<button>Нажми</button>'
});

Затем он используется как <my-button></my-button>.

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

export default {
  components: { MyButton }
}

В однофайловых компонентах (SFC) достаточно импортировать и зарегистрировать в components.

Типичная ошибка: использование зарезервированных имён (например, header, button) для компонентов - Vue игнорирует их. Имена компонентов должны быть многословными или иметь префикс.

Расширенные возможности Vue.js

Как создать переиспользуемую логику с composable?

Composable - это функция, использующая Composition API для инкапсуляции состояния и методов. Пример - счётчик с историей изменений:

Пример
import { ref, watch } from 'vue';

export function useCounter(initialValue = 0) {
  const count = ref(initialValue);
  const history = ref([]);

  const increment = () => {
    history.value.push({ old: count.value, new: count.value + 1 });
    count.value++;
  };

  return { count, history, increment };
}

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

Пример
setup() {
  const { count, history, increment } = useCounter(10);
  return { count, history, increment };
}
При каждом нажатии кнопки в массиве history появляется запись об изменении. Это удобно для отладки или отмены действий.

Как передавать данные от предка к потомкам без цепочки props?

Функции provide и inject позволяют «провайдить» значения для всех дочерних компонентов, независимо от глубины.

Пример
// Родительский компонент
setup() {
  const theme = ref('light');
  provide('theme', theme);
  return { theme };
}

// Глубокий потомок
setup() {
  const theme = inject('theme', 'default'); // второй аргумент - значение по умолчанию
  return { theme };
}
При изменении theme в родителе все инжектированные потомки автоматически обновляются. Ошибка: если забыть указать значение по умолчанию, при отсутствии провайдера будет undefined.

Как лениво загружать компоненты?

Асинхронные компоненты (defineAsyncComponent) загружаются только когда они понадобятся, уменьшая начальный размер бандла.

Пример
import { defineAsyncComponent } from 'vue';

const AsyncComp = defineAsyncComponent(() =>
  import('./MyLargeComponent.vue')
);

// Опционально: настройки загрузки и ошибки
const AsyncCompWithLoader = defineAsyncComponent({
  loader: () => import('./MyLargeComponent.vue'),
  loadingComponent: LoadingSpinner,
  errorComponent: ErrorMessage,
  delay: 200,
  timeout: 3000
});
В консоли разработчика видно, что MyLargeComponent.vue загружается только при первом рендере. Типичная проблема: неправильный путь импорта или отсутствие дефолтного экспорта в компоненте.

Как динамически переключать компоненты?

Элемент <component :is="..."> позволяет рендерить разные компоненты в зависимости от значения переменной.

Пример
<component :is="currentComponent" :key="currentComponent" />

В data:

Пример
data() {
  return {
    currentComponent: 'CompA'
  };
}

Для сохранения состояния переключаемого компонента используется <keep-alive>:

Пример
<keep-alive>
  <component :is="currentComponent" />
</keep-alive>
При переключении вкладок (Compa, CompB) состояние каждого компонента сохраняется. Без keep-alive они будут уничтожаться и создаваться заново.

Как обрабатывать ошибки внутри компонентов?

Хук errorCaptured в родительском компоненте ловит ошибки, произошедшие в дочерних компонентах, и позволяет выполнить кастомные действия (например, отправить в логи).

Пример
// Родитель
errorCaptured(err, instance, info) {
  console.error('Ошибка в компоненте:', err, info);
  // Вернуть false, чтобы остановить всплытие
  return false;
}
Если дочерний компонент бросает исключение (например, при обращении к несуществующему свойству), родитель перехватит его и выведет в консоль. Важно: errorCaptured не ловит асинхронные ошибки (например, в setTimeout); для них нужно использовать глобальный обработчик app.config.errorHandler.

Как создавать переходы и анимации?

Обёртка <Transition> применяет встроенные CSS-классы при вставке/удалении элемента.

Пример
<Transition name="fade">
  <p v-if="show">Анимированный текст</p>
</Transition>
Пример
.fade-enter-active,
.fade-leave-active {
  transition: opacity 0.5s;
}
.fade-enter-from,
.fade-leave-to {
  opacity: 0;
}
При переключении show текст плавно появляется и исчезает. Без Transition изменения происходят мгновенно. Ошибка: забыть определить CSS-классы с правильными именами (например, .fade-enter-from вместо .fade-enter).

Как использовать render-функции вместо шаблонов?

Иногда требуется полный контроль над виртуальным DOM. Render-функция возвращает VNode напрямую.

Пример
import { h } from 'vue';

export default {
  props: ['level'],
  render() {
    return h('h' + this.level, {}, this.$slots.default());
  }
};
Компонент динамически создаёт заголовок от h1 до h6 в зависимости от prop level. Без render-функции пришлось бы использовать много v-if.

Vue JS - comments

En
Vue js (javascript)