→ Vue.js principiantes lección 1: instancia Vue
→ Vue.js para principiantes, lección 2: atributos de enlace
→ Vue.js principiantes lección 3: renderizado condicional
→ Vue.js principiantes lección 4: listas de renderización
→ Vue .js para principiantes lección 5: procesamiento de eventos
→ Vue.js principiantes lección 6: clases y estilos vinculantes
→ Vue.js principiantes lección 7: propiedades calculadas
→ Vue.js principiantes lección 8: componentes
→ Vue. js para principiantes lección 9: eventos personalizados
→ Vue.js para principiantes lección 10: formularios
El propósito de la lección
Queremos tener pestañas en la página de la aplicación, una de las cuales permite a los visitantes escribir reseñas de productos y la otra les permite ver reseñas existentes.
Código inicial
Así es como se ve el contenido del archivo en esta etapa del trabajo
index.html
:
<div id="app">
<div class="cart">
<p>Cart({{ cart.length }})</p>
</div>
<product :premium="premium" @add-to-cart="updateCart"></product>
</div>
En
main.js
existe el siguiente código:
Vue.component('product', {
props: {
premium: {
type: Boolean,
required: true
}
},
template: `
<div class="product">
<div class="product-image">
<img :src="image" />
</div>
<div class="product-info">
<h1>{{ title }}</h1>
<p v-if="inStock">In stock</p>
<p v-else>Out of Stock</p>
<p>Shipping: {{ shipping }}</p>
<ul>
<li v-for="(detail, index) in details" :key="index">{{ detail }}</li>
</ul>
<div
class="color-box"
v-for="(variant, index) in variants"
:key="variant.variantId"
:style="{ backgroundColor: variant.variantColor }"
@mouseover="updateProduct(index)"
></div>
<button
@click="addToCart"
:disabled="!inStock"
:class="{ disabledButton: !inStock }"
>
Add to cart
</button>
</div>
<div>
<h2><font color="#3AC1EF">Reviews</font></h2>
<p v-if="!reviews.length">There are no reviews yet.</p>
<ul>
<li v-for="review in reviews">
<p>{{ review.name }}</p>
<p>Rating: {{ review.rating }}</p>
<p>{{ review.review }}</p>
</li>
</ul>
</div>
<product-review @review-submitted="addReview"></product-review>
</div>
`,
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
}
],
reviews: []
}
},
methods: {
addToCart() {
this.$emit('add-to-cart', this.variants[this.selectedVariant].variantId);
},
updateProduct(index) {
this.selectedVariant = index;
},
addReview(productReview) {
this.reviews.push(productReview)
}
},
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
}
}
}
})
Vue.component('product-review', {
template: `
<form class="review-form" @submit.prevent="onSubmit">
<p v-if="errors.length">
<b>Please correct the following error(s):</b>
<ul>
<li v-for="error in errors">{{ error }}</li>
</ul>
</p>
<p>
<label for="name">Name:</label>
<input id="name" v-model="name">
</p>
<p>
<label for="review">Review:</label>
<textarea id="review" v-model="review"></textarea>
</p>
<p>
<label for="rating">Rating:</label>
<select id="rating" v-model.number="rating">
<option>5</option>
<option>4</option>
<option>3</option>
<option>2</option>
<option>1</option>
</select>
</p>
<p>
<input type="submit" value="Submit">
</p>
</form>
`,
data() {
return {
name: null,
review: null,
rating: null,
errors: []
}
},
methods: {
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.")
}
}
}
})
var app = new Vue({
el: '#app',
data: {
premium: true,
cart: []
},
methods: {
updateCart(id) {
this.cart.push(id);
}
}
})
Así es como se ve la aplicación ahora.
Página de aplicación
Tarea
Actualmente, las reseñas y el formulario que se utiliza para enviar reseñas se muestran en la página contigua. Esta es una estructura bastante funcional. Pero se espera que aparezcan más y más reseñas en la página con el tiempo. Esto significa que será más conveniente para los usuarios interactuar con una página que, de su elección, muestre un formulario o una lista de reseñas.
La solucion del problema
Para solucionar nuestro problema, podemos agregar un sistema de pestañas a la página. Uno de ellos, con un título
Reviews
, mostrará reseñas. El segundo, con un título Make a Review
, mostrará un formulario para enviar reseñas.
Crear un componente que implemente el sistema de pestañas
Comencemos por crear un componente
product-tabs
. Se mostrará en la parte inferior de la representación visual del componente product
. Con el tiempo, reemplazará el código que se usa actualmente para mostrar la lista de revisiones y formularios en la página.
Vue.component('product-tabs', {
template: `
<div>
<span class="tab" v-for="(tab, index) in tabs" :key="index">{{ tab }}</span>
</div>
`,
data() {
return {
tabs: ['Reviews', 'Make a Review']
}
}
})
En este momento, esto es solo un componente en blanco que finalizaremos pronto. Por ahora, analicemos brevemente lo que se presenta en este código.
Los datos del componente tienen una matriz que
tabs
contiene las cadenas que usamos como encabezados de pestaña. La plantilla de componente utiliza una construcción v-for
que tabs
crea un elemento que <span>
contiene la cadena correspondiente para cada elemento de la matriz . Lo que forma este componente en esta etapa de trabajo se verá como el que se muestra a continuación.
El componente de pestañas de productos en la etapa inicial de trabajo en él
. Para lograr nuestros objetivos, necesitamos saber cuál de las pestañas está activa. Por lo tanto, agreguemos una propiedad a los datos del componente
selectedTab
. Estableceremos dinámicamente el valor de esta propiedad utilizando un controlador de eventos que responde a los clics en los títulos de las pestañas:
@click="selectedTab = tab"
La propiedad se escribirán líneas correspondientes a los títulos de las pestañas.
Es decir, si el usuario hace clic en la pestaña
Reviews
, selectedTab
se escribirá una cadena Reviews
. Si hace clic en la pestaña Make a Review
, se selectedTab
incluirá la línea Make a Review
.
Así es como se verá ahora el código completo del componente.
Vue.component('product-tabs', {
template: `
<div>
<ul>
<span class="tab"
v-for="(tab, index) in tabs"
@click="selectedTab = tab"
>{{ tab }}</span>
</ul>
</div>
`,
data() {
return {
tabs: ['Reviews', 'Make a Review'],
selectedTab: 'Reviews' // @click
}
}
})
Vincular una clase a una pestaña activa
Un usuario que trabaja con una interfaz que utiliza pestañas debe saber qué pestaña está activa. Puede implementar un mecanismo similar mediante el enlace de clases a los elementos que se
<span>
utilizan para mostrar los nombres de las pestañas:
:class="{ activeTab: selectedTab === tab }"
Aquí está el archivo CSS que define el estilo de la clase usada aquí
activeTab
. Así es como se ve este estilo:
.activeTab {
color: #16C0B0;
text-decoration: underline;
}
Y aquí está el estilo de la clase
tab
:
.tab {
margin-left: 20px;
cursor: pointer;
}
Si explica la construcción anterior en un lenguaje simple, resulta que el estilo especificado para la clase se aplica a la pestaña
activeTab
, en el caso de que sea selectedTab
igual tab
. Dado que el selectedTab
nombre de la pestaña en la que el usuario acaba de hacer clic está escrito, el estilo .activeTab
se aplicará específicamente a la pestaña activa.
En otras palabras, cuando el usuario haga clic en la primera pestaña,
tab
estará dentro Reviews
, se escribirá la misma selectedTab
. Como resultado, el estilo se aplicará a la primera pestaña .activeTab
.
Ahora, los títulos de las pestañas en la página se verán a continuación.
El título resaltado de la pestaña activa
Parece que todo está funcionando como se esperaba en esta etapa, por lo que podemos seguir adelante.
Trabajando en la plantilla de componentes
Ahora que podemos decirle al usuario qué pestaña es la activa, podemos seguir trabajando en el componente. Es decir, estamos hablando de finalizar su plantilla, describiendo qué se mostrará exactamente en la página cuando se active cada una de las pestañas.
Pensemos en lo que debería mostrarse al usuario si hace clic en la pestaña
Reviews
. Esto es, por supuesto, reseñas de productos. Por lo tanto, movamos el código para mostrar revisiones de la plantilla de componente product
a la plantilla de componente product-tabs
, colocando este código debajo de la construcción utilizada para mostrar los encabezados de las pestañas. Así es como se verá la plantilla de componentes ahora product-tabs
:
template: `
<div>
<ul>
<span class="tab"
:class="{ activeTab: selectedTab === tab }"
v-for="(tab, index) in tabs"
@click="selectedTab = tab"
>{{ tab }}</span>
</ul>
<div>
<p v-if="!reviews.length">There are no reviews yet.</p>
<ul>
<li v-for="review in reviews">
<p>{{ review.name }}</p>
<p>Rating: {{ review.rating }}</p>
<p>{{ review.review }}</p>
</li>
</ul>
</div>
</div>
`
Tenga en cuenta que nos deshicimos de la etiqueta
<h2><font color="#3AC1EF">
porque ya no necesitamos mostrar el título Reviews
sobre la lista de reseñas. En lugar de este título, se mostrará el título de la pestaña correspondiente.
Pero mover el código de la plantilla por sí solo no es suficiente para proporcionar comentarios. La matriz
reviews
cuyos datos se utilizan para mostrar reseñas se almacena como parte de los datos del componente product
. Necesitamos pasar esta matriz al componente product-tabs
usando el mecanismo de accesorios del componente . Agreguemos lo product-tabs
siguiente al objeto con las opciones utilizadas durante la creación :
props: {
reviews: {
type: Array,
required: false
}
}
Pasemos una matriz
reviews
de componente product
a componente product-tabs
utilizando la product
siguiente construcción en la plantilla :
<product-tabs :reviews="reviews"></product-tabs>
Ahora pensemos en lo que debe mostrarse en la página si el usuario hace clic en el título de la pestaña
Make a Review
. Este es, por supuesto, un formulario para enviar comentarios. Con el fin de preparar el proyecto para seguir trabajando en él, transfiramos el código de conexión product-review
del componente de la plantilla del componente product
a la plantilla product-tabs
. Coloquemos el siguiente código debajo del elemento <div>
utilizado para mostrar la lista de reseñas:
<div>
<product-review @review-submitted="addReview"></product-review>
</div>
Si mira la página de la aplicación ahora, encontrará que la lista de revisiones y el formulario se muestran debajo de los encabezados de las pestañas.
Una etapa intermedia de trabajo en la página
En este caso, los clics en los encabezados, aunque conducen a su selección, no afectan en modo alguno a otros elementos de la página. Además, si intenta utilizar el formulario, resulta que ha dejado de funcionar normalmente. Todas estas son consecuencias bastante esperadas de los cambios que hicimos en la aplicación. Sigamos trabajando y llevemos nuestro proyecto a un estado funcional.
Visualización condicional de elementos de página
Ahora que hemos preparado los elementos principales de la plantilla de componentes
product-tabs
, es hora de crear un sistema que le permitirá mostrar diferentes elementos de página según el título de la pestaña en el que el usuario hizo clic.
Los datos del componente ya tienen una propiedad
selectedTab
. Podemos usarlo en una directiva v-show
para renderizar condicionalmente lo que pertenece a cada una de las pestañas.
Entonces, a la etiqueta que
<div>
contiene el código para generar la lista de reseñas, podemos agregar la siguiente construcción:
v-show="selectedTab === 'Reviews'"
Gracias a ella, la lista de reseñas se mostrará cuando la pestaña esté activa
Reviews
.
De manera similar, agregaremos lo siguiente a la etiqueta que
<div>
contiene el código de conexión del componente product-review
:
v-show="selectedTab === 'Make a Review'"
Esto hará que el formulario se muestre solo cuando la pestaña esté activa
Make a Review
.
Así es como se verá la plantilla de componentes ahora
product-tabs
:
template: `
<div>
<ul>
<span class="tab"
:class="{ activeTab: selectedTab === tab }"
v-for="(tab, index) in tabs"
@click="selectedTab = tab"
>{{ tab }}</span>
</ul>
<div v-show="selectedTab === 'Reviews'">
<p v-if="!reviews.length">There are no reviews yet.</p>
<ul>
<li v-for="review in reviews">
<p>{{ review.name }}</p>
<p>Rating: {{ review.rating }}</p>
<p>{{ review.review }}</p>
</li>
</ul>
</div>
<div v-show="selectedTab === 'Make a Review'">
<product-review @review-submitted="addReview"></product-review>
</div>
</div>
`
Si miras la página y haces clic en las pestañas, puedes asegurarte de que el mecanismo que hemos creado funciona correctamente.
Al hacer clic en las pestañas, se ocultan algunos elementos y se muestran otros.
Enviar comentarios a través de un formulario aún no funciona. Investiguemos el problema y solucionémoslo.
Resolver el problema al enviar comentarios
Si observa la consola de herramientas para desarrolladores del navegador ahora, verá una advertencia.
Advertencia de consola
Aparentemente, el sistema no puede detectar el método
addReview
. ¿Lo que le sucedió?
Para responder a esta pregunta, recuerde que
addReview
es un método que se declara en un componenteproduct
. Debe llamarse si el componenteproduct-review
(y este es un componente hijo del componenteproduct
) genera un eventoreview-submitted
:
<product-review @review-submitted="addReview"></product-review>
Así es como funcionaba todo antes de transferir el fragmento de código anterior al componente
product-tabs
. Y ahora un componente product
es un componente hijo product-tabs
, y product-review
ahora no es un "hijo", un componente product
, sino su "nieto".
Nuestro código ahora está diseñado para interactuar
product-review
con el componente principal. Pero ahora ya no es un componente product
. Como resultado, resulta que para que el formulario funcione correctamente, necesitamos refactorizar el código del proyecto.
Refactorización del código del proyecto
Para asegurar la comunicación de los componentes del nieto con sus "abuelos", o para establecer la comunicación entre componentes del mismo nivel, a menudo se usa un mecanismo llamado bus de eventos globales.
El Global Event Bus es un canal de comunicación que se puede utilizar para transferir información entre componentes. Y es, de hecho, solo una instancia de Vue que se crea sin pasarle un objeto con opciones. Creemos un bus de eventos:
var eventBus = new Vue()
Este código irá al nivel superior del archivo
main.js
.
Puede que le resulte más fácil comprender este concepto si piensa en el autobús de eventos como un autobús. Sus pasajeros son datos que unos componentes envían a otros. En nuestro caso, estamos hablando de transferir información sobre eventos generados por otros componentes a un componente. Es decir, nuestro "bus" viajará de un componente
product-review
a otro product
, llevando la información que el formulario ha sido enviado y entregando los datos del formulario de product-review
a product
.
Ahora, en el componente
product-review
, en el método onSubmit
, hay una línea como esta:
this.$emit('review-submitted', productReview)
Reemplazémoslo con el siguiente, usando en su
eventBus
lugar this
:
eventBus.$emit('review-submitted', productReview)
Después de eso, ya no es necesario que escuche el evento del
review-submitted
componente product-review
. Por lo tanto, cambiaremos el código de este componente en la plantilla del componente product-tabs
a lo siguiente:
<product-review></product-review>
El
product
método ahora se puede eliminar del componente addReview
. En su lugar, usaremos la siguiente construcción:
eventBus.$on('review-submitted', productReview => {
this.reviews.push(productReview)
})
Hablaremos a continuación sobre cómo usarlo en un componente, pero por ahora, describiremos en pocas palabras lo que sucede en él. Esta construcción indica que cuando
eventBus
genera un evento review-submitted
, debe tomar los datos pasados en este evento (es decir, - productReview
) y colocarlos en la matriz de reviews
componentes product
. De hecho, esto es muy similar a lo que se ha hecho hasta ahora en un método addReview
que ya no necesitamos. Tenga en cuenta que el fragmento de código anterior utiliza una función de flecha. Este momento es digno de una cobertura más detallada.
Razones para usar una función de flecha
Aquí estamos usando la sintaxis de la función de flecha que se introdujo en ES6. El punto es que el contexto de la función de flecha está vinculado al contexto principal. Es decir, cuando dentro de esta función usamos una palabra clave
this
, es equivalente a la palabra clave this
que corresponde a la entidad que contiene la función flecha.
Este código se puede reescribir sin usar las funciones de flecha, pero luego debe organizar el enlace
this
:
eventBus.$on('review-submitted', function (productReview) {
this.reviews.push(productReview)
}.bind(this))
Completando el proyecto
Casi hemos alcanzado nuestra meta. Todo lo que queda por hacer es encontrar un lugar para el fragmento de código que proporcione una respuesta al evento
review-submitted
. Una product
función puede convertirse en un lugar en un componente mounted
:
mounted() {
eventBus.$on('review-submitted', productReview => {
this.reviews.push(productReview)
})
}
¿Qué es esta función? Este es un enlace de ciclo de vida que se llama una vez después de que el componente se monta en el DOM. Ahora, después de
product
montar el componente , esperará a que ocurran los eventos review-submitted
. Después de que se genera un evento de este tipo, lo que se pasa en este evento se agregará a los datos del componente, es decir, - productReview
.
Si ahora intenta dejar una reseña sobre el producto mediante el formulario, resulta que esta reseña se muestra donde debería estar.
La forma funciona como debería
El bus de eventos no es la mejor solución para comunicar componentes
Aunque el bus de eventos se utiliza a menudo, y aunque puede encontrarlo en varios proyectos, tenga en cuenta que está lejos de ser la mejor solución al problema de conectar componentes de la aplicación.
A medida que la aplicación crece, un sistema de gestión de estado basado en Vuex puede resultar muy útil . Es una biblioteca y un patrón de administración del estado de la aplicación.
Taller
Agregue pestañas
Shipping
y al proyecto Details
, que, respectivamente, muestran el costo de entrega de las compras y la información sobre los bienes.
- Aquí hay una plantilla que puede usar para resolver este problema.
- Aquí está la solución al problema.
Salir
Esto es lo que aprendió en este tutorial:
- Puede utilizar herramientas de representación condicional para organizar el mecanismo de las pestañas.
- , Vue, .
- — . . — Vuex.
Esperamos que después de tomar este curso de Vue, haya aprendido lo que quería y esté listo para aprender muchas más cosas nuevas e interesantes sobre este marco.
Si acaba de completar este curso, comparta sus impresiones.
→ Lección 1 para principiantes de Vue.js : instancia Vue
→ Vue.js para principiantes, lección 2: atributos de enlace
→ Lección 3 para principiantes de Vue.js: renderizado condicional
→ Lección 4 para principiantes de Vue.js: renderizado de listas
→ Vue .js para principiantes Lección 5: Manejo de eventos
→ Vue.js para principiantes, Lección 6: Clases y estilos vinculantes
→ Vue.js para principiantes, Lección 7: Propiedades calculadas
→Vue.js para principiantes, lección 8: Componentes
→ Vue.js para principiantes, lección 9: eventos personalizados
→ Vue.js para principiantes, lección 10: Formularios