Vue.JS

Componentes

David Daza

Full Stack Developer - Datasketch

 @ddazal

 @_daviddaza

Lugar · Bebidas · Comida

Agradecimientos

Recursos

Antes de empezar

Las interfaces de usuario pueden ser divididas en componentes

Barra de navegación

Footer

Carousel

Card

Card

Card

Card

Card

Sidebar

Las interfaces de usuario pueden ser abstraídas en un árbol de componentes

Bloques independientes y reutilizables de una interfaz

Vue.component

Vue.component('welcome', {
  template: '<h1>Welcome</h1>'
})

new Vue({
  el: '#app'
})

Vue.component

<div id="app">
  <welcome></welcome>
  <!-- <welcome /> -->
  <!-- <Welcome></Welcome> -->
  <!-- <Welcome /> -->
</div>

Vue.component

components

const Welcome = {
 template: '<h1>Welcome</h1>' 
}

new Vue({
  el: '#app',
  components: { Welcome }
})

components

<div id="app">
  <Welcome />
</div>

components

¿Cómo le pasamos datos a los componentes?

props

Vue.component('welcome', {
  props: ['name'],
  template: '<h1>Welcome, {{ name }}</h1>'
})

new Vue({
  el: '#app',
  data() {
    return {
      name: 'babies'    
    }
  }
})

props

<div id="app">
  <Welcome :name="name" />
</div>

props

props validation

Vue.component('welcome', {
  props: {
    name: {
      type: String,
      default: 'stranger',
      // required: true
      // validator(val) { return val.length > 1}
    }
  },
  template: '<h1>Welcome, {{ name }}</h1>'
})

new Vue({
  el: '#app',
  data() {
    return {
      name: 'babies'
    }
  }
})

props validation

Ejemplo

Ejemplo

Resumen

✅ Los componentes son bloques independientes y reutilizables de una interfaz

✅ Los componentes reciben datos mediante la propiedad props

data debe ser una función

✅ Un solo elemento raíz por componente

Flujo de datos

Padre

Hijo

pasa props

emite eventos

¿Cómo emitimos eventos?

$emit

const Counter = {
  props: {
    count: Number
  },
  template: `<button @click="increment">{{ count }}</button>`,
  methods: {
    increment() {
      this.$emit('increment')
    }
  }
}

new Vue({
  el: '#app',
  components: { Counter },
  data() {
    return {
      count: 0
    }
  },
  methods: {
    incrementCount() {
      this.count += 1
    }
  }
})

$emit

<div id="app">
  <Counter :count="count" @increment="incrementCount"/>
</div>

$emit

$emit - parámetros

const Counter = {
  props: {
    count: Number
  },
  template: `<button @click="increment">{{ count }}</button>`,
  methods: {
    increment() {
      this.$emit('increment', 2)
    }
  }
}

new Vue({
  el: '#app',
  components: { Counter },
  data() {
    return {
      count: 0
    }
  },
  methods: {
    incrementCount(factor) {
      this.count += factor
    }
  }
})

$emit - parámetros

$emit - Event

Vue.component('MouseTracker', {
  props: {
    pos: Object,
  },
  template: `
	<div class="mouse-tracker" @mousemove="$emit('track', $event)">
	</div>
  `
})

new Vue({
  el: '#app',
  data() {
    return {
      pos: { x: 0, y: 0 },
    }
  },
  methods: {
    updatePosition(event) {
      this.pos.x = event.clientX
      this.pos.y = event.clientY
    }
  }
})

$emit - Event

Resumen

✅ Los hijos pueden emitir eventos a sus padres utilizando el método $emit

✅ Los props no deben ser modificados por el componente que los recibe, sino por el componente padre

Para acceder al objeto Event, emite el evento con la variable $event

v-model

<input v-model="text" />
 
<input :value="text" @input="text = $event.target.value" />
 

v-model

✅ Enlazar el atributo value a un prop value

✅ Emitir el evento input con el nuevo valor

v-model

Vue.component('my-search', {
  props: ['value'],
  template: `
    <div class="my-search">
      <input type="text" placeholder="Search" :value="value" @input="$emit('input', $event.target.value)"/>
      <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><path d="M12.9 14.32a8 8 0 1 1 1.41-1.41l5.35 5.33-1.42 1.42-5.33-5.34zM8 14A6 6 0 1 0 8 2a6 6 0 0 0 0 12z"/></svg>
    </div>
  `
})

new Vue({
  el: '#app',
  data() {
    return {
      search: ''
    }
  }
})

v-model

<div id="app">
  <my-search v-model="search"></my-search>
</div>

v-model

Ciclo de vida

✅ beforeCreate

✅ created

✅ beforeMount

✅ mounted

✅ beforeUpdate

✅ updated

✅ beforeDestroy

✅ destroyed

beforeCreate

Es llamado antes de que la instancia pueda observar los datos y configurar eventos

created

La instancia configura los datos, computed properties, métodos, watch/event callbacks.

beforeMount

$el está disponible

mounted

Se hace render del DOM

beforeUpdate

Es llamado cuando cambian los datos, pero antes de actualizar el DOM

updated

Es llamado cuando el DOM se ha actualizado con el cambio de los datos

beforeDestroy

Es llamado justo antes de destruir la instancia/componente

destroyed

El componente ha sido eliminado totalmente

Break

🧘🏽‍♀️🧘🏾‍♂️

Slots

Slots

<div class="notification">
  <div class="notification-body">
    <p>Account verified</p>
    <div class="notification-dismiss">
    <button>
      <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><path d="M2.93 17.07A10 10 0 1 1 17.07 2.93 10 10 0 0 1 2.93 17.07zM11.4 10l2.83-2.83-1.41-1.41L10 8.59 7.17 5.76 5.76 7.17 8.59 10l-2.83 2.83 1.41 1.41L10 11.41l2.83 2.83 1.41-1.41L11.41 10z"/></svg>
    </button>
  </div>
  </div>
</div>

Slots

<div class="notification">
  <div class="notification-body">
    <slot></slot>
    <div class="notification-dismiss">
    <button>
      <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><path d="M2.93 17.07A10 10 0 1 1 17.07 2.93 10 10 0 0 1 2.93 17.07zM11.4 10l2.83-2.83-1.41-1.41L10 8.59 7.17 5.76 5.76 7.17 8.59 10l-2.83 2.83 1.41 1.41L10 11.41l2.83 2.83 1.41-1.41L11.41 10z"/></svg>
    </button>
  </div>
  </div>
</div>

Slots

Vue.component('notification', {
  template: `
    <div class="notification">
      <div class="notification-body">
        <slot></slot>
        <div class="notification-dismiss">
          <button>
            <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><path d="M2.93 17.07A10 10 0 1 1 17.07 2.93 10 10 0 0 1 2.93 17.07zM11.4 10l2.83-2.83-1.41-1.41L10 8.59 7.17 5.76 5.76 7.17 8.59 10l-2.83 2.83 1.41 1.41L10 11.41l2.83 2.83 1.41-1.41L11.41 10z"/></svg>
          </button>
        </div>
      </div>
    </div>
  `
})

new Vue({
  el: '#app'
})

Slots

<div id="app">
  <notification>
    <p>Account verified!</p>
  </notification>
</div>

Slots

Named slots

Named slots

<div class="modal">
  <div class="modal-header">
    <button>
      <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><path d="M2.93 17.07A10 10 0 1 1 17.07 2.93 10 10 0 0 1 2.93 17.07zM11.4 10l2.83-2.83-1.41-1.41L10 8.59 7.17 5.76 5.76 7.17 8.59 10l-2.83 2.83 1.41 1.41L10 11.41l2.83 2.83 1.41-1.41L11.41 10z"/></svg>
    </button>
  </div>
  <div class="modal-body">
    <h3 class="modal-title">Payment successful!</h3>
    <p class="modal-content">Thank you for shopping with us! We are lucky to have customers like you so your order will be as soon as possible in your door ❤️</p>
  </div>
  <div class="modal-footer">
    <button>Done</button>
  </div>
</div>

Named slots

<div class="modal">
  <div class="modal-header">
    <button>
      <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><path d="M2.93 17.07A10 10 0 1 1 17.07 2.93 10 10 0 0 1 2.93 17.07zM11.4 10l2.83-2.83-1.41-1.41L10 8.59 7.17 5.76 5.76 7.17 8.59 10l-2.83 2.83 1.41 1.41L10 11.41l2.83 2.83 1.41-1.41L11.41 10z"/></svg>
    </button>
  </div>
  <div class="modal-body">
    <h3 class="modal-title"><slot name="title"></slot></h3>
    <p class="modal-content"><slot name="content"></slot></p>
  </div>
  <div class="modal-footer">
    <button><slot name="cta"></slot></button>
  </div>
</div>

Named slots

Vue.component('my-modal', {
  template: `
    <div class="modal">
      <div class="modal-header">
        <button>
          <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><path d="M2.93 17.07A10 10 0 1 1 17.07 2.93 10 10 0 0 1 2.93 17.07zM11.4 10l2.83-2.83-1.41-1.41L10 8.59 7.17 5.76 5.76 7.17 8.59 10l-2.83 2.83 1.41 1.41L10 11.41l2.83 2.83 1.41-1.41L11.41 10z"/></svg>
        </button>
      </div>
      <div class="modal-body">
        <h3 class="modal-title"><slot name="title"></slot></h3>
        <p class="modal-content"><slot name="content"></slot></p>
      </div>
      <div class="modal-footer">
        <button><slot name="cta"></slot></button>
      </div>
    </div>
  `
})

new Vue({
  el: '#app'
})

Named slots

<div id="app">
  <my-modal>
    <template v-slot:title>
      Payment successful!
    </template>
    <template v-slot:content>
      Thank you for shopping with us! We are lucky to have customers like you so your order will be as soon as possible in your door ❤️
    </template>
    <template v-slot:cta>
      Done
    </template>
  </my-modal>
</div>

Named slots

<div id="app">
  <my-modal>
    <template #title>
      Payment successful!
    </template>
    <template #content>
      Thank you for shopping with us! We are lucky to have customers like you so your order will be as soon as possible in your door ❤️
    </template>
    <template #cta>
      Done
    </template>
  </my-modal>
</div>

Named slots

Named slots ⚠️

<div id="app">
  <my-modal>
    <template slot="title">
      Payment successful!
    </template>
    <template slot="content">
      Thank you for shopping with us! We are lucky to have customers like you so your order will be as soon as possible in your door ❤️
    </template>
    <template slot="cta">
      Done
    </template>
  </my-modal>
</div>

Scoped slots

Scoped slots

Vue.component('user-card', {
  template: `
	<div>
	  <slot>{{ user.firstname }} {{ user.lastname }} is {{ user.status }}</slot>
	</div>`,
  data() {
    return {
      user: {
        firstname: 'Jane',
        lastname: 'Doe',
        status: 'connected'
      }
    }
  }
})

new Vue({
  el: '#app'
})

Scoped slots

<div id="app">
  <user-card></user-card>
</div>

Scoped slots

Scoped slots

<div id="app">
  <user-card>
  	Yes! {{ user.firstname }} is {{ user.status }}
  </user-card>
</div>

Scoped slots

Scoped slots

Vue.component('user-card', {
  template: `
	<div>
	  <slot v-bind:user="user">{{ user.firstname }} {{ user.lastname }} is {{ user.status }}</slot>
	</div>`,
  data() {
    return {
      user: {
        firstname: 'Jane',
        lastname: 'Doe',
        status: 'connected'
      }
    }
  }
})

new Vue({
  el: '#app'
})

Scoped slots

<div id="app">
  <user-card v-slot="slotProps">
  	Yes! {{ slotProps.user.firstname }} is {{ slotProps.user.status }}
  </user-card>
</div>

Scoped slots

<div id="app">
  <user-card v-slot="{ user }">
  	Yes! {{ user.firstname }} is {{ user.status }}
  </user-card>
</div>

Scoped slots

Ejemplo

Componentes dinámicos

<component>

Vue.component('tab-home', {
  template: '<div>Home component</div>'
});

Vue.component('tab-posts', {
  template: '<div>Posts component</div>'
});

Vue.component('tab-archive', {
  template: '<div>Archive component</div>'
});

new Vue({
  el: '#app',
  data: {
    currentTab: 'Home',
    tabs: ['Home', 'Posts', 'Archive']
  },
  computed: {
    currentTabComponent: function() {
      return 'tab-' + this.currentTab.toLowerCase();
    }
  }
});

<component>

<div id="app">
  <button v-for="tab in tabs" :class="['tab-button', { active: currentTab === tab }]" @click="currentTab = tab">
    {{ tab }}
  </button>
  <component v-bind:is="currentTabComponent" class="tab"></component>
</div>

<component>

Ejemplo

¡GRACIAS!

 @ddazal

 @_daviddaza

Vue.js Components

By David Daza

Vue.js Components

Slides para Meetup de Vue.JS en Bogotá. Febrero 10, 2020.

  • 529