Vue.js: подробное руководство по фреймворку
Основы работы с 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в зависимости от proplevel. Без render-функции пришлось бы использовать многоv-if.