[Перевод] Vue.js для начинающих, урок 10: формы
Сегодня, в 10 уроке курса по Vue, мы поговорим о том, как работать с формами. Формы позволяют собирать данные, вводимые пользователем. Кроме того, здесь мы обсудим валидацию форм, то есть — проверку того, что в них вводят.
→ Vue.js для начинающих, урок 1: экземпляр Vue
→ Vue.js для начинающих, урок 2: привязка атрибутов
→ Vue.js для начинающих, урок 3: условный рендеринг
→ Vue.js для начинающих, урок 4: рендеринг списков
→ Vue.js для начинающих, урок 5: обработка событий
→ Vue.js для начинающих, урок 6: привязка классов и стилей
→ Vue.js для начинающих, урок 7: вычисляемые свойства
→ Vue.js для начинающих, урок 8: компоненты
→ Vue.js для начинающих, урок 9: пользовательские события
Цель урока
Мы собираемся создать форму, которая позволяет посетителям сайта отправлять отзывы на товары. При этом нужно, чтобы отзыв можно было бы отправить только в том случае, если заполнены все поля формы, которые обязательно должны быть заполнены.
Начальный вариант кода
Вот что сейчас находится в index.html
:
Cart({{ cart.length }})
Так выглядит main.js
:
Vue.component('product', {
props: {
premium: {
type: Boolean,
required: true
}
},
template: `
{{ title }}
In stock
Out of Stock
Shipping: {{ shipping }}
- {{ detail }}
`,
data() {
return {
product: 'Socks',
brand: 'Vue Mastery',
selectedVariant: 0,
details: ['80% cotton', '20% polyester', 'Gender-neutral'],
variants: [
{
variantId: 2234,
variantColor: 'green',
variantImage: './assets/vmSocks-green.jpg',
variantQuantity: 10
},
{
variantId: 2235,
variantColor: 'blue',
variantImage: './assets/vmSocks-blue.jpg',
variantQuantity: 0
}
]
}
},
methods: {
addToCart() {
this.$emit('add-to-cart', this.variants[this.selectedVariant].variantId);
},
updateProduct(index) {
this.selectedVariant = index;
console.log(index);
}
},
computed: {
title() {
return this.brand + ' ' + this.product;
},
image() {
return this.variants[this.selectedVariant].variantImage;
},
inStock() {
return this.variants[this.selectedVariant].variantQuantity;
},
shipping() {
if (this.premium) {
return "Free";
} else {
return 2.99
}
}
}
})
var app = new Vue({
el: '#app',
data: {
premium: true,
cart: []
},
methods: {
updateCart(id) {
this.cart.push(id);
}
}
})
Задача
Нам надо, чтобы посетители сайта могли бы оставлять отзывы на товары, но на нашем сайте пока нет средств, позволяющих получать данные от пользователей. Такими средствами являются формы.
Решение задачи
Нам, для решения стоящей перед нами задачи, нужно создать форму. Начнём работу с создания нового компонента, предназначенного специально для работы с формой. Назовём этот компонент product-review
. Такое имя выбрано из-за того, что компонент будет обеспечивать работу формы, направленной на сбор отзывов (review) о товарах. Компонент product-review
будет вложен в компонент product
.
Зарегистрируем новый компонент, приступим к формированию его шаблона и оснастим его некоторыми данными:
Vue.component('product-review', {
template: `
`,
data() {
return {
name: null
}
}
})
Как видите, в шаблоне компонента есть элемент , а в данных компонента есть свойство
data
, пока пустое.
Как привязать то, что пользователь вводит в поле, к свойству name
?
На предыдущих занятиях мы говорили о привязке данных с использованием директивы v-bind
, но тогда мы рассматривали лишь одностороннюю привязку. Поток данных шёл от свойства, хранящего данные, к элементу управления, визуализирующего их. А теперь нам надо, чтобы то, что пользователь вводит в поле, оказывалось бы в свойстве name
, хранящемся в составе данных компонента. Другими словами, нам нужно, чтобы поток данных был бы направлен от поля ввода к свойству.
Директива v-model
Директива v-model
позволяет организовывать двустороннюю привязку данных. При такой схеме работы, если что-то новое оказывается в поле ввода, это приводит к изменению данных. И, соответственно, когда меняются данные, обновляется состояние элемента управления, эти данные использующего.
Добавим к полю ввода директиву v-model
и привяжем это поле к свойству name
из данных компонента.
Теперь добавим в шаблон компонента полный код формы:
Как видите, директивой v-model
оснащены поля input
, textarea
и select
. Обратите внимание на то, что при настройке поля select
использован модификатор .number
(подробнее о нём мы поговорим ниже). Это позволяет обеспечить преобразование соответствующих данных к типу Number
, в то время как обычно они представлены в строковом виде.
Дополним набор данных компонента, добавив в него те данные, к которым привязаны вышеописанные элементы управления:
data() {
return {
name: null,
review: null,
rating: null
}
}
В верхней части шаблона формы можно видеть, что при отправке формы вызывается метод onSubmit
. Скоро мы создадим этот метод. Но сначала давайте поговорим о том, какую роль играет конструкция .prevent
.
Это — модификатор события. Он предотвращает перезагрузку страницы после возникновения события submit
. Есть и другие полезные модификаторы событий. О них мы, правда, говорить не будем.
Теперь мы готовы к созданию метода onSubmit
. Начнём с такого кода:
onSubmit() {
let productReview = {
name: this.name,
review: this.review,
rating: this.rating
}
this.name = null
this.review = null
this.rating = null
}
Как видите, в этом методе, на основе данных, введённых пользователем, создаётся объект. Ссылка на него записывается в переменную productReview
. Здесь же мы сбрасываем в null
значения свойств name
, review
, rating
. Но работа пока не окончена. Нам ещё нужно куда-то отправить productReview
. Куда же отправлять этот объект?
Отзывы о товаре имеет смысл хранить там же, где хранятся данные компонента product
. Учитывая то, что компонент product-review
вложен в компонент product
, мы можем сказать, что product-review
— это дочерний компонент компонента product
. Как мы уже выяснили на предыдущем занятии, для отправки данных от дочерних компонентов родительским можно использовать события, генерируемые с помощью $emit
.
Доработаем метод onSubmit
:
onSubmit() {
let productReview = {
name: this.name,
review: this.review,
rating: this.rating
}
this.$emit('review-submitted', productReview)
this.name = null
this.review = null
this.rating = null
}
Теперь мы генерируем событие с именем review-submitted
и передаём в нём только что созданный объект productReview
.
Далее, нам надо организовать прослушивание этого события, разместив в шаблоне product
следующее:
Эта строка читается так: «Когда происходит событие review-submitted
, нужно запустить метод addReview
компонента product
».
Вот код этого метода:
addReview(productReview) {
this.reviews.push(productReview)
}
Этот метод берёт объект productReview
, пришедший от метода onSubmit
, а после этого помещает его в массив reviews
, хранящийся в данных компонента product
. Но такого массива в данных этого компонента пока нет. Поэтому добавим его туда:
reviews: []
Замечательно! Теперь элементы формы привязаны к данным компонента product-review
. Эти данные используются для создания объекта productReview
. А этот объект передаётся, при отправке формы, компоненту product
. В итоге объект productReview
добавляется в массив reviews
, который хранится в данных компонента product
.
Вывод отзывов о товаре
Теперь осталось лишь вывести на странице товара отзывы, оставленные посетителями сайта. Делать мы это будем в компоненте product
, поместив соответствующий код выше кода, с помощью которого компонент product-review
размещён в компоненте product
.
Reviews
There are no reviews yet.
-
{{ review.name }}
Rating: {{ review.rating }}
{{ review.review }}
Тут мы создаём список отзывов, пользуясь директивой v-for
, и выводим данные, хранящиеся в объекте review
, используя точечную нотацию.
В теге мы проверяем, есть ли что-то в массиве
reviews
(проверяя его длину). Если в массиве ничего нет — мы выводим сообщение There are no reviews yet
.
Страница с формой для ввода отзывов
Проверка форм
В формах часто бывают поля, которые, перед отправкой формы, должны быть обязательно заполнены. Например, нам не хотелось бы, чтобы пользователи отправляли бы отзывы, в которых поле текста отзыва было бы пустым.
К нашему счастью, HTML5 поддерживает атрибут required
. Выглядит его использование так:
Подобная конструкция приведёт к автоматическому выводу сообщения об ошибке в том случае, если пользователь попытается отправить форму, в которой required-поле окажется пустым.
Сообщение об ошибке
Наличие в браузере стандартных механизмов проверки полей форм — это очень приятно, так как это может освободить нас от создания собственных механизмов проверки полей. Но то, как проводится стандартная проверка данных, может, в каком-то особом случае, нас не устроить. В такой ситуации вполне логично будет написать собственный код проверки форм.
Пользовательская проверка форм
Поговорим о том, как создать собственную систему проверки форм.
Включим в состав данных компонента product-review
массив для хранения сообщений об ошибках. Назовём его errors
:
data() {
return {
name: null,
review: null,
rating: null,
errors: []
}
}
Мы хотели бы добавлять в этот массив сведения об ошибках, возникающих в ситуациях, когда поля формы оказываются пустыми. Речь идёт о следующем коде:
if(!this.name) this.errors.push("Name required.")
if(!this.review) this.errors.push("Review required.")
if(!this.rating) this.errors.push("Rating required.")
Первая из этих строк сообщает системе о том, что если поле name
не заполнено, нужно поместить в массив errors
сообщение об ошибке Name required
. Аналогично работают и другие строки, проверяющие поля review
и rating
. Если любое из них окажется пустым — в массив array
попадёт сообщение об ошибке.
А куда поместить этот код?
Так как мы хотим, чтобы сообщения об ошибках записывались бы в массив только в тех случаях, когда при попытке отправки формы оказывается, что поля name
, review
или submit
не заполнены, мы можем разместить этот код в методе onSubmit
. Мы, кроме того, перепишем тот код, что в нём уже есть, добавив в него некоторые проверки:
onSubmit() {
if(this.name && this.review && this.rating) {
let productReview = {
name: this.name,
review: this.review,
rating: this.rating
}
this.$emit('review-submitted', productReview)
this.name = null
this.review = null
this.rating = null
} else {
if(!this.name) this.errors.push("Name required.")
if(!this.review) this.errors.push("Review required.")
if(!this.rating) this.errors.push("Rating required.")
}
}
Теперь мы проверяем поля name
, review
и rating
. Если во всех этих полях имеются данные — мы создаём на их основе объект productReview
и отправляем его родительскому компоненту. Затем значение соответствующих свойств сбрасывается.
Если же хотя бы одно из полей оказывается пустым, мы помещаем в массив errors
сообщение об ошибке, зависящее от того, что именно не ввёл пользователь перед отправкой формы.
Осталось лишь вывести эти ошибки, что можно сделать с помощью следующего кода:
Please correct the following error(s):
- {{ error }}
Тут используется директива v-if
, с помощью которой мы проверяем массив errors
на предмет наличия в нём сообщений об ошибках (фактически, анализируем длину массива). Если в массиве что-то есть, выводится элемент , который, применяя
v-for
, выводит список ошибок из массива errors
.
Сообщения об ошибках
Теперь у нас имеется собственная система валидации формы.
Использование модификатора .number
Модификатор .number
, используемый в директиве v-model
, способен принести немалую пользу. Но, применяя его, учитывайте то, что с ним связана одна проблема. Если соответствующее значение окажется пустым, оно будет представлено строкой, а не числом. В «Книге рецептов Vue» предлагается решение этой проблемы. Оно заключается в явном преобразовании соответствующего значения к числовому типу:
Number(this.myNumber)
Практикум
Добавьте в форму следующий вопрос: «Would you recommend this product?». Сделайте так, чтобы пользователь мог бы ответить на него, пользуясь радиокнопками «yes» и «no». Проведите проверку ответа на этот вопрос и включите соответствующие данные в объект productReview
.
- Вот заготовка, которую вы можете использовать для решения этой задачи.
- Вот решение задачи.
Итоги
Сегодня мы говорили о работе с формами. Вот самое важное из того, что вы сегодня узнали:
- Для организации двусторонней привязки данных к элементам форм можно использовать директиву
v-model
. - Модификатор
.number
сообщает Vue о том, что соответствующее значение нужно приводить к числовому типу. Но при работе с ним есть одна проблема, связанная с тем, что пустые значения остаются строками. - Модификатор события
.prevent
позволяет предотвратить перезагрузку страницы после отправки формы. - Средствами Vue можно реализовать достаточно простой механизм пользовательской валидации форм.
Получилось ли у вас сегодняшнее домашнее задание?
→ Vue.js для начинающих, урок 1: экземпляр Vue
→ Vue.js для начинающих, урок 2: привязка атрибутов
→ Vue.js для начинающих, урок 3: условный рендеринг
→ Vue.js для начинающих, урок 4: рендеринг списков
→ Vue.js для начинающих, урок 5: обработка событий
→ Vue.js для начинающих, урок 6: привязка классов и стилей
→ Vue.js для начинающих, урок 7: вычисляемые свойства
→ Vue.js для начинающих, урок 8: компоненты
→ Vue.js для начинающих, урок 9: пользовательские события