Теория
Во вью реализован компонентный подход для реализации приложения
Так же вью решает проблему реактивности в веб-приложениях. При обычном подходе, страница не знает, что у неё изменилось значение какого-либо элемента
При использовании вью, мы создаём модели, которые представляют собой данные, которые мы можем изменять в шаблонах
Те данные, которые мы описываем под изменения, называются моделями. Под капотом они оборачиваются в прокси, который срабатывает на получение и установку модели и тем самым срабатывает рендер
Начало разработки. Создание проекта
Устанавливаем CLI от Vue и создаём с его помощью проект
npm i -g @vue/cli
vue create <project>
И таким образом будет выглядеть стоковый проект
Компонент App
В основном файле JS мы подключаем корневой компонент, в который и будут складываться все компоненты
src > main.js
import { createApp } from 'vue';
import App from './components/App.vue';
createApp(App).mount('#app');
И таким образом выглядит стоковый компонент vue:
template
- шаблон компонентаscript
- скрипты компонентаstyle
- стили компонента
src > components > App.vue
// Single File Component
<template>
<!-- HTML code -->
</template>
<script setup>
// JS code
</script>
<style scoped>
/* CSS code */
</style>
Интерполяция
Внутрь двойных скобок {{ переменная }}
мы можем помещать переменные из this
скоупа
src > components > App.vue
<template>
<div>
<h2>Количество лайков {{ likes }}</h2>
<button>+</button>
</div>
</template>
<script>
export default {
data() {
return {
likes: 0,
};
},
};
</script>
<style>
h1 {
font-family: Montserrat, sans-serif;
}
</style>
Methods. V-ON. Слушатели событий
Из скрипта мы можем экспортировать методы, которые мы будем использовать внутри событий вёрстки. Чтобы подключить события вью, мы можем воспользоваться конструкцией v-on:
или его аналогом @
src > components > App.vue
<template>
<div>
<h2>Лайки {{ likes }}</h2>
<h2>Дизлайки {{ dislikes }}</h2>
<div>
<button v-on:click="addLike">+</button>
<button @click="addDislike">-</button>
</div>
</div>
</template>
<script>
export default {
data() {
return {
likes: 0,
dislikes: 0,
};
},
methods: {
addLike() {
this.likes += 1;
},
addDislike() {
this.dislikes += 1;
},
},
};
</script>
<style>
h1 {
font-family: Montserratm, sans-serif;
}
</style>
Vue devtools. Инструменты разработчика
Cтили
Флаг scoped
говорит нам о том, что стили будут сохраняться в одном компоненте и не будут уходить в глобалку
<template>
<div class="post">
<div><strong>Название</strong> JavaScript</div>
<div><strong>Описание</strong> JS - универсальный язык</div>
</div>
<div class="post">
<div><strong>Название</strong> JavaScript</div>
<div><strong>Описание</strong> JS - универсальный язык</div>
</div>
</template>
<script>
export default {
data() {
return {
likes: 0,
dislikes: 0,
};
},
methods: {
addLike() {
this.likes += 1;
},
addDislike() {
this.dislikes += 1;
},
},
};
</script>
<style scoped>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: Montserratm, sans-serif;
}
.post {
padding: 15px;
margin-top: 15px;
border: 2px solid #8951fd;
}
</style>
Отрисовка в цикле. Директива V-FOR
Для создания итерируемой конструкции для перебора имеющихся данных во вью существует две директивы:
v-for
- в ней мы указываем, перебор какого массива будетv-bind:key
- присваиваем ключ для каждого элемента перебора
<template>
<div class="post" v-for="post in posts" v-bind:key="post.id">
<div><strong>Название</strong> {{ post.title }}</div>
<div><strong>Описание</strong> {{ post.body }}</div>
</div>
</template>
<script>
export default {
data() {
return {
posts: [
{ id: 1, title: 'JavaScript', body: 'JS - is universal language' },
{ id: 2, title: 'C#', body: 'C# - is beautiful language' },
{ id: 3, title: 'Java', body: 'Java - is banking language' },
],
};
},
methods: {},
};
</script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: Montserratm, sans-serif;
}
.post {
padding: 15px;
margin-top: 15px;
border: 2px solid #8951fd;
}
</style>
Двустороннее связывание
Для реализации добавления поста на страницу, нужно сначала реализовать двусторонне связывание, которое позволит синхронизировать данные из инпутов с данными компонента
Конкретно для связывания данных во вью используется v-bind
, который привязывает одни данные под другие
И так же нам нужен будет атрибут @input
, который хранит в себе метод присвоения определённых данных из формы на странице к форме вью (он будет передавать наши введённые данные в данные компонента)
<template>
<div class="app">
<form action="">
<h4>Создать пост</h4>
<input
v-bind:value="title"
@input="inputTitle"
type="text"
class="input"
placeholder="название"
/>
<input
v-bind:value="body"
@input="inputBody"
type="text"
class="input"
placeholder="описание"
/>
<button @click="createPost">Добавить</button>
</form>
<div class="post" v-for="post in posts" v-bind:key="post.id">
<div><strong>Название</strong> {{ post.title }}</div>
<div><strong>Описание</strong> {{ post.body }}</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
posts: [
{ id: 1, title: 'JavaScript', body: 'JS - is universal language' },
{ id: 2, title: 'C#', body: 'C# - is beautiful language' },
{ id: 3, title: 'Java', body: 'Java - is banking language' },
],
title: '',
body: '',
};
},
methods: {
createPost() {},
inputTitle(event) {
this.title = event.target.value;
},
inputBody(event) {
this.body = event.target.value;
},
},
};
</script>
Так же можно сократить запись и вместо отдельного метода просто указать, что мы приравниваем нужное нам значение к зарезервированному ивенту $event.target.value
<template>
<div class="app">
<form action="">
<h4>Создать пост</h4>
<input
v-bind:value="title"
@input="title = $event.target.value"
type="text"
class="input"
placeholder="название"
/>
<input
v-bind:value="body"
@input="body = $event.target.value"
type="text"
class="input"
placeholder="описание"
/>
<button @click="createPost">Добавить</button>
</form>
<div class="post" v-for="post in posts" v-bind:key="post.id">
<div><strong>Название</strong> {{ post.title }}</div>
<div><strong>Описание</strong> {{ post.body }}</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
posts: [
{ id: 1, title: 'JavaScript', body: 'JS - is universal language' },
{ id: 2, title: 'C#', body: 'C# - is beautiful language' },
{ id: 3, title: 'Java', body: 'Java - is banking language' },
],
title: '',
body: '',
};
},
methods: {
createPost() {},
},
};
</script>
Модификаторы stop, prevent
Далее реализуем метод createPost
, через который будем добавлять новые посты. Но тут встаёт проблема, что страница перезагружается при отправке формы.
Чтобы исправить вышеописанную проблему, можно передать в функцию event
и прописать stopPropagation
или preventDefault
, но вью предоставляет модификатор @submit
, который в себе имеет prevent
, останавливающий выполнение ивентов при сабмите формы
<template>
<div class="app">
<form action="" @submit.prevent>
<h4>Создать пост</h4>
<input
v-bind:value="title"
@input="title = $event.target.value"
type="text"
class="input"
placeholder="название"
/>
<input
v-bind:value="body"
@input="body = $event.target.value"
type="text"
class="input"
placeholder="описание"
/>
<button @click="createPost">Добавить</button>
</form>
<div class="post" v-for="post in posts" v-bind:key="post.id">
<div><strong>Название</strong> {{ post.title }}</div>
<div><strong>Описание</strong> {{ post.body }}</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
posts: [
{ id: 1, title: 'JavaScript', body: 'JS - is universal language' },
{ id: 2, title: 'C#', body: 'C# - is beautiful language' },
{ id: 3, title: 'Java', body: 'Java - is banking language' },
],
title: '',
body: '',
};
},
methods: {
createPost(event) {
const newPost = {
id: Date.now(),
title: this.title,
body: this.body,
};
this.posts.push(newPost);
this.title = '';
this.body = '';
},
},
};
</script>
Декомпозиция. Создаем переиспользуемые компоненты. Props. Передаем данные в компонент
Первым делом вынесем список постов. Он должен будет извне принимать список постов. Чтобы указать принимаемые пропсы в компонент, нужно указать внутрь props
нужное значение, прописать его типы и указать обязателен ли этот пропс
PostList.vue
<template>
<div class="post" v-for="post in posts" v-bind:key="post.id">
<div><strong>Название</strong> {{ post.title }}</div>
<div><strong>Описание</strong> {{ post.body }}</div>
</div>
</template>
<script>
export default {
props: {
posts: {
type: Array,
required: true,
},
},
};
</script>
<style scoped>
.post {
padding: 15px;
margin-top: 15px;
border: 2px solid #8951fd;
}
</style>
Компонент формы, который пока не может создавать новые посты
PostForm.vue
<template>
<form action="" @submit.prevent>
<h4>Создать пост</h4>
<input
v-bind:value="post.title"
@input="post.title = $event.target.value"
type="text"
class="input"
placeholder="название"
/>
<input
v-bind:value="post.body"
@input="post.body = $event.target.value"
type="text"
class="input"
placeholder="описание"
/>
<button>Добавить</button>
</form>
</template>
<script>
export default {
data() {
return {
post: {
title: '',
body: '',
},
};
},
};
</script>
<style scoped>
.input {
display: flex;
width: 50%;
padding: 5px;
margin: 15px 0;
border: 1px solid #8951fd;
}
</style>
И теперь, чтобы подключить дочерние компоненты в целевой, нужно:
- Импортировать нужные компоненты в скриптах
- Проинициализировать их в блоке
components
- Добавить их в в сам компонент в темплейте
Чтобы передать пропсы внутрь компонента, нужно через v-bind
прописать те пропсы, которые принимает компонент и указать объект с пропсами.
Так же, чтобы кратко записать биндинг достаточно написать вместо v-bind
просто “:
”
App.vue
<template>
<div class="app">
<post-form />
<post-list :posts="posts" />
</div>
</template>
<script>
import PostList from '@/components/PostList.vue';
import PostForm from '@/components/PostForm.vue';
export default {
components: {
PostForm,
PostList,
},
data() {
return {
posts: [
{ id: 1, title: 'JavaScript', body: 'JS - is universal language' },
{ id: 2, title: 'C#', body: 'C# - is beautiful language' },
{ id: 3, title: 'Java', body: 'Java - is banking language' },
],
};
},
};
</script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: Montserratm, sans-serif;
}
.app {
padding: 15px;
}
button {
padding: 5px;
color: #8951fd;
border: 1px solid #8951fd;
}
</style>
Пропсы изменять в дочернем компоненте нельзя. Нужно их изменять в родительском и передавать в готовом виде уже в дочерний!
V-MODEL
Стандартное двусторонне связывание предусматривает под собой написание правильных событий и атрибутов и постоянное повторение данной записи.
Можно упростить связывание через указание простого атрибута v-model
, который за нас реализует указание правильных событий, атрибутов, функций присвоения значений.
Так же далее создадим заново функцию создания поста.
Сейчас перед нами встаёт задача передачи данных для создания нового поста в компонент со списком постов
PostList.vue
<template>
<form action="" @submit.prevent>
<h4>Создать пост</h4>
<input
v-model="post.title"
type="text"
class="input"
placeholder="название"
/>
<input
v-model="post.body"
type="text"
class="input"
placeholder="описание"
/>
<button @click="createPost">Добавить</button>
</form>
</template>
<script>
export default {
data() {
return {
post: {
title: '',
body: '',
},
};
},
methods: {
createPost() {
this.post.id = Date.now();
this.posts.push(newPost);
this.title = '';
this.body = '';
},
},
};
</script>
<style scoped>
.input {
display: flex;
width: 50%;
padding: 5px;
margin: 15px 0;
border: 1px solid #8951fd;
}
</style>
$emit. Обмен данными между дочерним и родительским компонентом
Передать данные от ребёнка к родителю напрямую - не получится. Чтобы совершить данное действие придётся воспользоваться событием, на которое нужно будет подписать родителя, чтобы тот получил данные из ребёнка.
Для этого нужно будет воспользоваться $emit
. В него мы передаём название события первым аргументом, а последующими передаём данные, которые попадут в функцию, которая будет подписана на данное событие
В форме постов мы создаём метод createPost
внутри которого эмитим ивент, в который передаём новый пост, а так же ещё несколько пропсов для примера.
PostForm.vue
<template>
<form action="" @submit.prevent>
<h4>Создать пост</h4>
<input v-model="post.title" type="text" class="input" placeholder="название" />
<input v-model="post.body" type="text" class="input" placeholder="описание" />
<button @click="createPost">Добавить</button>
</form>
</template>
<script>
export default {
data() {
return {
post: {
title: '',
body: '',
},
};
},
methods: {
createPost() {
this.post.id = Date.now();
this.$emit('create', this.post, 'second', 'third');
this.post = {
title: '',
body: '',
};
},
},
};
</script>
Чтобы воспользоваться ивентом, нужно:
- На компонент, который эмитит событие, нужно навесить атрибут
@имя_события = функция_которая_примет_пропсы
- В родительском компоненте создаём метод, который уже из описанного в темплейте события будет принимать пропсы
App.vue
<template>
<div class="app">
<post-form @create="createPost" />
<post-list :posts="posts" />
</div>
</template>
<script>
import PostList from '@/components/PostList.vue';
import PostForm from '@/components/PostForm.vue';
export default {
components: {
PostForm,
PostList,
},
data() {
return {
posts: [
{ id: 1, title: 'JavaScript', body: 'JS - is universal language' },
{ id: 2, title: 'C#', body: 'C# - is beautiful language' },
{ id: 3, title: 'Java', body: 'Java - is banking language' },
],
};
},
methods: {
createPost(post, second, third) {
console.log(second);
console.log(third);
this.posts.push(post);
},
},
};
</script>
И далее стоит разбить пост в отдельный компонент, а список постов в другой
PostItem.vue
<template>
<div class="post">
<div>
<div><strong>Название</strong> {{ post.title }}</div>
<div><strong>Описание</strong> {{ post.body }}</div>
</div>
<div class="post__buttons">
<button>Удалить</button>
</div>
</div>
</template>
<script>
export default {
props: {
post: {
type: Object,
required: true,
},
},
};
</script>
<style scoped>
.post {
display: flex;
align-items: center;
justify-content: space-between;
padding: 15px;
margin-top: 15px;
border: 2px solid #8951fd;
}
</style>
PostList.vue
<template>
<div>
<h2>Список постов</h2>
<post-item v-for="post in posts" :key="post.id" :post="post" />
</div>
</template>
<script>
import PostItem from '@/components/PostItem.vue';
export default {
components: {
PostItem,
},
props: {
posts: {
type: Array,
required: true,
},
},
};
</script>
<style scoped></style>
Библиотека UI компонентов
Если мы хотим создать компонент, внутрь которого можно будет поместить какой-либо текст, то нам нужно будет вложить в него тег slot
, который означает, что все вложенные внутрь компонента элементы будут помещаться в данном месте
components > ui > UiButton.vue
<template>
<button>
<slot></slot>
</button>
</template>
<script>
export default {};
</script>
<style scoped>
button {
padding: 5px;
color: #8951fd;
border: 1px solid #8951fd;
background: none;
cursor: pointer;
}
</style>
Так же нужно упомянуть, что все стили и классы, которые мы помещаем в компонент, применяются на корневой элемент внутри компонента
Глобальная регистрация компонента
Чтобы не импортировать каждый раз компонент UI в другие компоненты, можно глобально зарегистрировать компонент
Для этого нужно сначала дать имя самому компоненту
`components > ui > UiButton.vue
<template>
<button>
<slot></slot>
</button>
</template>
<script>
export default {
name: 'ui-button',
};
</script>
Далее нужно экспортировать массив компонентов
components > ui > index.js
import UiButton from '@/components/ui/UiButton.vue';
export default [UiButton];
И уже затем нужно глобально к приложению эти компоненты подцепить через итерацию массива
src > main.js
import { createApp } from 'vue';
import App from './components/App.vue';
import components from '@/components/ui';
const app = createApp(App);
components.forEach((component) => {
app.component(component.name, component);
});
app.mount('#app');
И теперь можно спокойно использовать компонент в приложении без импорта его во все файлы
Подробно о V-MODEL
Во второй версии Vue на весь компонент распространялся только один v-model
. В третьей версии мы можем указать конкретно к чему мы хотим указать двусторонне связывание. Однако, если мы не укажем с чем мы хотим связать значения, то по умолчанию связывание будет с пропсом modelValue
компонента
Чтобы реализовать связывание по стандартному modelValue
без указания конкретного значения через атрибут, мы можем написать такой код и сбиндить значение инпута со значением модели через событие, которое будет срабатывать на update
components > ui > UiInput.vue
<template>
<input
:value="modelValue" // 1
@input="updateInput" // 2
class="input" type="text"
/>
</template>
<script>
export default {
name: 'ui-input',
props: {
modelValue: [String, Number], // 3
},
methods: {
updateInput(event) {
this.$emit('update:modelValue', event.target.value); // 4
},
},
};
</script>
Если мы хотим своё кастомное значение, то мы можем указать своё кастомное значение для связывания нашего кастомного компонента инпута
`components > ui > UiInput.vue
<template>
<input :value="value" @input="updateInput" class="input" type="text" />
</template>
<script>
export default {
name: 'ui-input',
props: {
value: [String, Number],
},
methods: {
updateInput(event) {
this.$emit('update:value', event.target.value);
},
},
};
</script>
И при указании модели, нужно будет дополнить связывание атрибутом value
Удаление поста. Ключи KEY в цикле
Чтобы реализовать возможность удаления поста, нам нужно заэмиттить событие, которое будет возвращать пост наверх из самого элемента поста по клику на кнопку удаления поста
PostItem.vue
<template>
<div class="post">
<div>
<div><strong>Название</strong> {{ post.title }}</div>
<div><strong>Описание</strong> {{ post.body }}</div>
</div>
<div class="post__buttons">
<ui-button @click="$emit('remove', post)">Удалить</ui-button>
</div>
</div>
</template>
И далее мы принимаем из события remove
пост и заново его передаём вверх
PostList.vue
<template>
<div>
<h2>Список постов</h2>
<post-item
v-for="post in posts"
:key="post.id"
:post="post"
@remove="$emit('remove', post)"
/>
</div>
</template>
Теперь мы можем в родительском компоненте затриггерить функцию удаления поста, в которую сразу попадёт пропс с постом и позволит отрисовать новый массив постов без поста с определённым id
App.vue
<template>
<div class="app">
<post-form @create="createPost" />
<post-list :posts="posts" @remove="removePost" />
</div>
</template>
<script>
import PostList from '@/components/PostList.vue';
import PostForm from '@/components/PostForm.vue';
export default {
components: {
PostForm,
PostList,
},
data() {
return {
posts: [
{ id: 1, title: 'JavaScript', body: 'JS - is universal language' },
{ id: 2, title: 'C#', body: 'C# - is beautiful language' },
{ id: 3, title: 'Java', body: 'Java - is banking language' },
],
};
},
methods: {
createPost(post, second, third) {
console.log(second);
console.log(third);
this.posts.push(post);
},
removePost(post) {
this.posts = this.posts.filter((p) => p.id !== post.id);
},
},
};
</script>
Отрисовка по условию
Для отрисовки определённых блоков по условию у нас есть три разных атрибута:
v-if
- отрендерит, если условие будет удовлетвореноv-else-if
v-else
- отрендерит, если прошлое условие не будет удовлетворено
Эти атрибуты вставляют нужный код, если он будет соответствовать условию.
PostList.vue
<template>
<div v-if="posts.length !== 0">
<h2>Список постов</h2>
<post-item
v-for="post in posts"
:key="post.id"
:post="post"
@remove="$emit('remove', post)"
/>
</div>
<div v-else-if="posts.length === 0">
<h2>Постов нет</h2>
</div>
<div v-else>
<h2>Произошла ошибка</h2>
</div>
</template>
Так же у нас есть атрибут, который просто скрывает элемент, если он не соответствует условию - v-show
PostList.vue
<template>
<div v-show="posts.length !== 0">
<h2>Список постов</h2>
<post-item
v-for="post in posts"
:key="post.id"
:post="post"
@remove="$emit('remove', post)"
/>
</div>
<div v-show="posts.length === 0">
<h2>Постов нет</h2>
</div>
</template>
Модальное окно
Само модальное окно. Мы его отображаем, если пропс show
будет true
. Скрывать модальное окно мы будем через поднятие состояния вверх через эмиттер.
components > ui > ModalWindow.vue
<template>
<div class="modal" v-if="show" @click="hideDialog">
<div class="modal__content" @click.stop>
<slot></slot>
</div>
</div>
</template>
<script>
export default {
name: 'modal-window',
props: {
show: {
type: Boolean,
default: false,
},
},
methods: {
hideDialog() {
this.$emit('update:show', false);
},
},
};
</script>
<style scoped>
.modal {
position: fixed;
top: 0;
bottom: 0;
right: 0;
left: 0;
display: flex;
background: rgba(0, 0, 0, 0.5);
}
.modal__content {
min-height: 100px;
min-width: 300px;
margin: auto;
padding: 20px;
background: white;
border-radius: 12px;
}
</style>
В родительском компоненте отображать модалку будем через использование функции showDialog
и пропса show
, который двусторонне связываем и передаём локальное значение dialogVisible
App.vue
<template>
<div class="app">
<ui-button @click="showDialog">Добавить пост</ui-button>
<modal-window v-model:show="dialogVisible">
<post-form @create="createPost" />
</modal-window>
<post-list :posts="posts" @remove="removePost" />
</div>
</template>
<script>
import PostList from '@/components/PostList.vue';
import PostForm from '@/components/PostForm.vue';
import UiButton from '@/components/ui/UiButton.vue';
export default {
components: {
UiButton,
PostForm,
PostList,
},
data() {
return {
posts: [
{ id: 1, title: 'JavaScript', body: 'JS - is universal language' },
{ id: 2, title: 'C#', body: 'C# - is beautiful language' },
{ id: 3, title: 'Java', body: 'Java - is banking language' },
],
dialogVisible: false,
};
},
methods: {
createPost(post, second, third) {
console.log(second);
console.log(third);
this.posts.push(post);
},
removePost(post) {
this.posts = this.posts.filter((p) => p.id !== post.id);
},
showDialog() {
this.dialogVisible = true;
},
},
};
</script>
Модификаторы V-MODEL
Модификаторы добавляются через точку и модифицируют конечную функциональность v-model.trim.number
. trim
срезает пробелы, а number
переводит значение сразу к числу
App.vue
<template>
<div class="app">
<h1>Страница с постами</h1>
<input type="text" v-model.trim.number="modificatorValue" />
<ui-button style="margin: 15px 0" @click="showDialog">Добавить пост</ui-button>
<modal-window v-model:show="dialogVisible">
<post-form @create="createPost" />
</modal-window>
<post-list :posts="posts" @remove="removePost" />
</div>
</template>
<script>
import PostList from '@/components/PostList.vue';
import PostForm from '@/components/PostForm.vue';
import UiButton from '@/components/ui/UiButton.vue';
export default {
components: {
UiButton,
PostForm,
PostList,
},
data() {
return {
posts: [
{ id: 1, title: 'JavaScript', body: 'JS - is universal language' },
{ id: 2, title: 'C#', body: 'C# - is beautiful language' },
{ id: 3, title: 'Java', body: 'Java - is banking language' },
],
dialogVisible: false,
modificatorValue: '',
};
},
methods: {
createPost(post, second, third) {
console.log(second);
console.log(third);
this.posts.push(post);
},
removePost(post) {
this.posts = this.posts.filter((p) => p.id !== post.id);
},
showDialog() {
this.dialogVisible = true;
},
},
};
</script>
Работа с сервером. Получаем посты. Axios
Добавляем метод fetchPosts
, который будет срабатывать при нажатии кнопки. Все полученные данные с сервера будут сразу заноситься в свойство posts
App.vue
<template>
<div class="app">
<h1>Страница с постами</h1>
<ui-button @click="fetchPosts">Получить посты</ui-button>
<ui-button style="margin: 15px 0" @click="showDialog">Добавить пост</ui-button>
<modal-window v-model:show="dialogVisible">
<post-form @create="createPost" />
</modal-window>
<post-list :posts="posts" @remove="removePost" />
</div>
</template>
<script>
import PostList from '@/components/PostList.vue';
import PostForm from '@/components/PostForm.vue';
import UiButton from '@/components/ui/UiButton.vue';
import axios from 'axios';
export default {
components: {
UiButton,
PostForm,
PostList,
},
data() {
return {
posts: [],
dialogVisible: false,
};
},
methods: {
createPost(post, second, third) {
console.log(second);
console.log(third);
this.posts.push(post);
},
removePost(post) {
this.posts = this.posts.filter((p) => p.id !== post.id);
},
showDialog() {
this.dialogVisible = true;
},
async fetchPosts() {
try {
const { data } = await axios.get(
'https://jsonplaceholder.typicode.com/posts?_limit=10',
);
this.posts = data;
} catch (e) {
throw new Error(e);
}
},
},
};
</script>
Жизненный цикл компонента
У нас есть достаточное количество различных хуков, которые работают на протяжении всего цикла существования vue-компонента:
beforeCreate
- вызывается перед созданием компонента. То есть тут ещё не инициализировались события и жизненный циклcreated
- тут уже стоит вызывать условия на проверки и что-то инициализировать. В этот момент в компонент добавляются инъекции и реактивностьbeforeMount
- отрабатывает до того, как элемент встроится в дом-деревоmounted
- отработает после встраиванияbeforeUpdate
- до обновленияupdated
- выполняется после обновления компонентаbeforeDestroy
- до уничтожения компонентаdestroyed
- после его уничтожения из дом-дерева
Индикатор загрузки данных
Первым делом нужно вызывать загрузку постов в хуке mounted
, который при монтировке компонента подгрузит нужные данные
Далее зададим поле isPostsLoading
, которое будем менять внутри функции fetchPosts
. Данное поле будет использоваться для условия отображения
App.vue
<template>
<div class="app">
<h1>Страница с постами</h1>
<ui-button style="margin: 15px 0" @click="showDialog">Добавить пост</ui-button>
<modal-window v-model:show="dialogVisible">
<post-form @create="createPost" />
</modal-window>
<post-list :posts="posts" @remove="removePost" v-if="!isPostsLoading" />
<div v-else>Идёт загрузка...</div>
</div>
</template>
<script>
import PostList from '@/components/PostList.vue';
import PostForm from '@/components/PostForm.vue';
import axios from 'axios';
export default {
components: {
PostForm,
PostList,
},
data() {
return {
posts: [],
dialogVisible: false,
isPostsLoading: false,
};
},
methods: {
createPost(post, second, third) {
console.log(second);
console.log(third);
this.posts.push(post);
},
removePost(post) {
this.posts = this.posts.filter((p) => p.id !== post.id);
},
showDialog() {
this.dialogVisible = true;
},
async fetchPosts() {
try {
this.isPostsLoading = true;
const { data } = await axios.get(
'https://jsonplaceholder.typicode.com/posts?_limit=10',
);
this.posts = data;
} catch (e) {
throw new Error(e);
} finally {
this.isPostsLoading = false;
}
},
},
mounted() {
this.fetchPosts();
},
};
</script>