Versiones comparadas

Clave

  • Se ha añadido esta línea.
  • Se ha eliminado esta línea.
  • El formato se ha cambiado.

Tabla de contenidos

...

Cada componente se define como un archivo .vue, cuya estructura básica es esta, independientemente de si el componente es una vista, o una parte de esta. Disponemos de dos formas de construir nuestros componentes, que vamos a ver a continuación.

La estructura básica de un componente es la siguiente:

Bloque de código
languagexml
<template>
    <!-- Bloque HTML -->
</template>

<script>
export default {
    // Funcionalidad Javascript
};
</script>

<style scoped>
    // Estilos CSS del componente
</style>

<i18n>
	// Internacionalización
</i18n>

Como podemos ver, estos archivos se dividen en diferentes bloques. En este caso vemos tres, pero hay alguno más que podemos incluir.

Template: código HTML

En el bloque template se incluye el código HTML del código. Se trata de HTML enriquecido, pues podremos referenciar a variables y métodos definidos en la parte JavaScript del componente. Hay que tener en cuenta que sólo puede haber un componente HTML en el primer nivel. Por ejemplo, teniendo todo lo que contenga envuelto en un div. Vamos a ver la sintaxis de las funciones que nos ofrece este HTML enriquecido.

...

Bloque de código
languagexml
<button v-on:click="counter += 1">Add 1</button>
<button @click="counter += 1">Add 1</button>

Script

El bloque script contiene la funcionalidad JavaScript del componente, pudiendo definir tanto variables propias como métodos, así como modificar el funcionamiento del propio componente. Para entender cómo funcionan los componentes, y cómo modificar su comportamiento, tenemos que conocer su ciclo de vida:

Image Added

Image RemovedEn este diagrama podemos ver los diferentes pasos que se siguen, y podemos ver que antes y después de los diferentes estados salen en rojo, señalados con una línea de puntos, nombres de funciones. Estas funciones son las llamadas llamadas lifecycle hooks, y nos permiten ejecutar código en esos momentos. Esto nos puede ser útil, por ejemplo, para cargar algún valor llamando a un servicio REST antes de que se monte el componente. Para definir estos métodos, lo haremos dentro del bloque script, y dentro del export default { ... }.

A partir de aquí, tenemos que diferenciar entre dos formas de organizar el bloque script, Options API Composition API. Options API es la forma tradicional de organizar los componentes, la que ya utilizábamos en Vue 2 y que sigue estando disponible en Vue 3. A continuación vamos a ver cada una de ellas, a partir de aquí porque los lifecycle hooks ya se nombran de forma diferente.

Options API

Las diferentes opciones, aunque podemos verlas también en el diagrama, son:

  • beforeCreate() { ... }
  • created() { ... }
  • beforeMount() { ... }
  • mounted() { ... }
  • beforeUpdate() { ... }
  • upadated() { ... }
  • beforeUnmount(){ ... } para Vue 3, beforeDestroy() { ... } para Vue 2.
  • unmounted(){ ... } para Vue 3, destroyed()   { ... } para Vue 2.

* Como vemos, los dos últimos cambian su sintaxis en Vue 3. Ahora mismo, el portal está en modo compatibilidad, los componentes se toman como si fueran de Vue 2, por lo que se seguirá utilizando la sintaxis Vue 2, pero esto es transitorio, pues se acabará terminando de migrar todo a Vue 3.

Además de definir estos métodos, dentro del export default { ... } también podemos definir los siguientes campos:

Image Added

  • name: nombre del componente. Si no lo definimos, por defecto será el del archivo.
  • props: variables que se le pasarán como parámetro en el template.
  • data(): variables definidas internamente en el propio componente, con alcance local.
  • computed(): variables calculadas.
  • methods: métodos definidos en el componente.
  • watch: listeners, funciones que se ejecutarán cuando el valor de la variable que indiquemos que se observa cambie.
  • components: otros componentes que se usan en el template. Para registrar componentes localmente.
  • emits: lista de eventos personalizados que pueden ser emitidos desde el componente.

Antes del export default { ... } se pueden importar otros componentes para registrarlos localmente, o archivos JavaScript para utilizar su funcionalidad. Hay una página de documentación dedicada a cómo registrar componentes.

...

Bloque de código
languagejs
<script>
import OtroComponente from './OtroComponente.vue';

export default {
  name: Componente,
  props: ['prop1', 'prop2'],
  data() {
    return {
      mensaje: "Hola"
    }
  },
  computed: {
    mensajeAlReves: function() {
      return this.mensaje.reverse();
    }
  },
  methods: {
    hacerCosas() {
      // Código js
    }
  },
  watch: {
    mensaje: function(newValue, oldValue) {
      console.log("El mensaje ha cambiado de '" + oldValue + "' a '" + newValue + "'");
    }
  },
  emits: ["algo", "otroEvento"],
  components: {
    OtroComponente
  },
  beforeCreate() {},
  created() {},
  beforeMount() {},
  mounted() {},
  beforeUpdate() {},
  updated() {},
  beforeDestroy() {},
  destroyed() {}
}
</script>

Composition API

Composition API es una forma de organizar los componentes que permite una mayor flexibilidad a la hora de organizar el código y una mayor reusabilidad de éste. Ahora en lugar de la estructura:

Bloque de código
languagejs
<script>
export default {
    ...
}
</script>

Tendremos:

Bloque de código
languagejs
<script setup>
    ...
</script>

Dentro podemos escribir el código que queramos, y será ejecutando. Tendremos que ir definiendo las diferentes variables, que se compartirán con el template, por ejemplo, los diferentes métodos, etc. Este setup se ejecuta al inicio del ciclo de vida, y desaparecen beforeCreate y created, pues lo que incluyamos se ejecutará en ese mismo punto. Las variables y métodos que definamos aquí serán expuestas al template, es decir, podrán ser accedidas desde éste.

Vamos a ir viendo las cosas que se utilizan de forma diferente:

Lifecycle hooks

Los lifecycle hooks, en Composition API, tenemos que importarlos de vue, y definirlos de esta forma:

Bloque de código
languagejs
<script setup>
import { onUpdated } from 'vue'

onUpdated(() => {
  ...
})
</script>

Todos se definirán de esta forma. A continuación tenemos la sintaxis de cada uno en Composition API (y a qué equivalen en Options API):

Options APIComposition APIDescripción
beforeCreate-Se escribe directamente en el setup
created-Se escribe directamente en el setup
beforeMountonBeforeMountSe ejecuta justo antes de que se monte el componente.
mountedonMountedSe ejecuta una vez se ha montado el componente.
beforeUpdateonBeforeUpdateSe ejecuta antes de una actualización del DOM.
updatedonUpdatedSe ejecuta cada vez que se actualiza el DOM por un cambio de estado reactivo.
beforeUnmountonBeforeUnmountSe ejecuta antes de quitar el componente.
unmountedonUnmountedSe ejecuta una vez se ha quitado el componente.

Props y emits

En lugar de en un bloque props, lo haremos así:

Bloque de código
languagejs
<script setup>
const props = defineProps({
  foo: String
})

const emit = defineEmits(['change', 'delete'])

...

</script>

ref() y reactive(): variables del componente

Mientras que antes teníamos que definir las variables reactivas del componente dentro del bloque data, ahora podemos definirlas con dos métodos, ref() y reactive(). Las variables que definamos con éstos, como decimos, serán reactivas, es decir, el template se enterará de los cambios en estas variables, reaccionará a ellos. Una variable definida de manera normal no será reactiva, el DOM no se actualizará cuando su valor cambie. Para utilizarlo, tenemos que importarlos primero, y se utilizan así:

Bloque de código
languagejs
import { ref, reactive } from 'vue';

const count = ref(0);

console.log(count.value)

const person = reactive({
  name: 'Albert',
  age: 30,
  isNinja: true,
});

console.log(person)

Vamos a ver las diferencias entre cada uno:

Ref actúa como un un wrapper para valores reactivos. Devuelve un objeto con un campo .value que contiene el valor, y que nos permite mutarlo.

Reactive funciona de forma similar, pero los objetos no son wrappeados, podemos acceder al valor directamente sin necesidad de especificar value.

Entonces... ¿Cuál es la diferencia? ¿Cuándo usar uno u otro? La principal diferencia entre ellos es que reactive sólo acepta objetos, no valores primitivos. Sin embargo ref sí que acepta valores primitivos. En realidad, ref utiliza reactive, ref(0) sería equivalente a reactive({ value : 0}). Ref también acepta objetos, con la diferencia de que sí se puede reasignar un objeto completo, reasignando .value, mientras que reactive no, se pueden cambiar los campos del objeto pero no se puede cambiar por un objeto diferente. Sabiendo esto, utilizaremos:

  • ref() cuando sea un valor primitivo, o un objeto o array que queramos reasignar más tarde.
  • reactive() cuando sea un objeto que no necesites reasignar, evitando así tener que pasar por .value para acceder a o modificar su valor.


En el template, para acceder a sus valores, no hace falta poner .value, se lee directamente. 

Estas son las opciones que tenemos, pero realmente podemos utilizar ref() para todo. Pues permite lo mismo que reactive y más (valores primitivos, reemplazar objeto), y el uso de .value hará que seamos conscientes siempre de que estamos trabajando con una variable que es reactiva para el template, de modo que tengamos una diferenciación entre estas variables y las variables normales, y tratemos con ellas siempre del mismo modo.

Computed

Para las variables computadas, disponemos de la función computed(). Cada una la definiremos así:

Bloque de código
languagejs
const color = computed(() => {
  if(contador.value < 0){
    return 'red'
  }else{
    return 'blue'
  }
})

Watchers

Para reaccionar a cambios en variables que requieran algo más complejo que un computed, disponemos de watch(). A esta, se le especifica la variable a observar, y una función cuyos parámetros son el valor anterior y el nuevo.

Bloque de código
languagejs
watch(data, (currentValue, oldValue) => {
  console.log(currentValue);
  console.log(oldValue);
});

Esto, con valores primitivos es bastante simple, pero se puede complicar para valores complejos. Mientras no tengamos documentado esto, podéis mirar esta guía, que es bastante completa: https://www.netlify.com/blog/2021/01/29/deep-dive-into-the-vue-composition-apis-watch-method/

Métodos

Como con la sintaxis que estamos utilizando, se están exponiendo al template las variables y métodos que utilizamos, podemos definir lo que antes teníamos dentro de methods como funciones normales:

Bloque de código
languagejs
const aumentar = () => {
  contador.value ++
}

También podemos importar métodos definidos en otros archivos, proporcionando así más flexibilidad y reutilización del código, sin tener que pasar por mixins. Esto se utiliza generalmente para hacer lo que se llaman funciones "composable", funciones que encapsulan lógica reutilizable sin estado. Estas funciones, por convención, se llaman con nomenclatura camelCase, y empiezan por "use".

Por ejemplo, podríamos tener una función que obtenga la posición del cursor en todo momento. Para ello, tendríamos un archivo aparte así:

Bloque de código
languagejs
// mouse.js
import { ref, onMounted, onUnmounted } from 'vue'

// by convention, composable function names start with "use"
export function useMouse() {
  // state encapsulated and managed by the composable
  const x = ref(0)
  const y = ref(0)

  // a composable can update its managed state over time.
  function update(event) {
    x.value = event.pageX
    y.value = event.pageY
  }

  // a composable can also hook into its owner component's
  // lifecycle to setup and teardown side effects.
  onMounted(() => window.addEventListener('mousemove', update))
  onUnmounted(() => window.removeEventListener('mousemove', update))

  // expose managed state as return value
  return { x, y }
}

Y luego simplemente importarlo y utilizarlo en componentes:

Bloque de código
languagejs
<script setup>
import { useMouse } from './mouse.js'

const { x, y } = useMouse()
</script>

<template>Mouse position is at: {{ x }}, {{ y }}</template>

Emit: emitir eventos

Para que nuestro componente lance eventos, devemos definir los que puede lanzar con defineEmits(), y después podremos lanzarlos directamente con emit():

Bloque de código
languagejs
<script setup>
const emit = defineEmits(['eventA', 'eventB'])
function btnClick(params) {
    emit('eventA')
    emit('eventB', params)
}
</script>

Importar componentes

Para registrar componentes de forma local, es decir, en otros componentes, en Composition API vale simplemente con importarlos:

Bloque de código
languagejs
<template>
  <MyComponent />
</template>

<script setup>
import MyComponent from './MyComponent.vue'
</script>

Sin embargo, en Options API, tenemos que importarlos y añadirlos al bloque components:

Bloque de código
languagejs
<template>
  <MyComponent />
</template>

<script setup>
import MyComponent from './MyComponent.vue'

export default {
    components: {
        MyComponent,
    }
}
</script>

También se pueden registrar componentes de forma global, para que estén disponibles desde todos los componentes del proyecto, pero esto no lo haremos, pues tenemos un sólo proyecto para todos los servicios del Portal de Servicios.

Style

Los bloques style incluyen estilos css. En este caso, se pueden tener varios bloques style:

  • <styles>: estilos globales, se aplicarán a todo el proyecto.
  • <styles scoped>: se aplica sólo al componente. Estos no se aplican tampoco a componentes hijos, para eso podemos utilizar ::v-deep, de esta forma:

    Bloque de código
    languagecss
    <style scoped>
    #tabla-asignaturas ::v-deep .p-datatable-tbody tr td {
     border: none !important;
    }
    #tabla-asignaturas ::v-deep .p-datatable-row {
     vertical-align: top;
    }
    </style>


i18n

En la página de documentación de internacionalización podemos ver cómo incluir un bloque para especificar las cadenas de texto y sus traducciones en el propio componente.